土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

John the Ripperでshadowファイルのパスワードを解読してみる

前回 は、Flask で実装した Pythonスクリプトを Cython化してみました。

今回は、参考書にしている「ハッキング・ラボのつくりかた 完全版 仮想環境におけるハッカー体験学習」で解説されていた shadowファイルについて、実際の shadowファイルを使って、内容を理解し、解析までやってみたいと思います。

それでは、やっていきます。

参考文献

はじめに

「セキュリティ」の記事一覧です。良かったら参考にしてください。

セキュリティの記事一覧

開発環境は、VirtualBox + ParrotOS 6.1 です。

今回のシリーズで参考書としている「ハッキング・ラボのつくりかた 完全版 仮想環境におけるハッカー体験学習」ですが、とても良い本です。

約1180ページあるので、見た目は辞書ですし、持ち歩くのは大変で、寝る前に布団に入って読むのも難しいです(笑)。

ハッキングの解説が主な内容のはずなんですが、ハッキングのための事前知識の説明に、多くのページを割いています。感覚的に半分ぐらいはハッキングの事前知識かもしれません。

この事前知識とは、Linux の基本的な(膨大な)知識であり、本来は非常に難解な内容のはずですが、とても分かりやすく説明してくれています。

私の場合、Linux は長い間使ってきたので、だいたい分かってた気になってましたが、この本を読み進めてると、まだまだ知る必要のある内容は多いなと感じました。

あまり、つぶやかないタイプだと思います(日本語が下手なので)が、たまには思ったことも書くのもいいな、と思いました。

では、今回もやっていきます。

Linuxのユーザ名とパスワードの管理方法

Linux では、ユーザがログインするときに入力されるユーザ名とパスワードの認証を、以下のファイルで管理しています。

  • /etc/passwd
  • /etc/shadow

それぞれを読んでみます(長いので一部だけ貼ります)。shadowファイルの方は、読むのに root権限が必要なので、root に切り替えておきます。

具体的にログインするのは root と user だけで、それ以外はデーモンで動作しているシステムユーザでログインは出来ないようになっています(nologin となっている)。

$ sudo su
# ll /etc/passwd /etc/shadow
-rw-r--r-- 1 root root   2.9K Jun  3 23:46 /etc/passwd
-rw-r----- 1 root shadow 1.3K Jun  4 04:38 /etc/shadow

# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
user:x:1000:1000::/home/user:/bin/bash
mysql:x:116:127:MySQL Server,,,:/nonexistent:/bin/false
tcpdump:x:117:128::/nonexistent:/usr/sbin/nologin
sshd:x:118:65534::/run/sshd:/usr/sbin/nologin

# cat /etc/shadow
root:*:19877:0:99999:7:::
www-data:*:19877:0:99999:7:::
nobody:*:19877:0:99999:7:::
user:$y$j9T$rtHFK6THpa78Gy39URhRO/$HCih5lqJBNxKCKy/L7sWsT3z1phVm5AnN8BapO7rHW9:19877:0:99999:7:::
mysql:!:19877::::::
tcpdump:!:19877::::::
sshd:!:19877::::::

今回注目したいのは、root と user の、passwd の第2フィールドと、shadow の第2フィールドです(区切り文字は : です)。

まず、passwd の第2フィールドは、ユーザのパスワードのハッシュ値が格納されます。しかし、root と user の x になっていて、この場合は、シャドウパスワードという仕組みを使っているという意味になります。

そこで、次は、shadowファイルの方の第2フィールドです。

root の方は * になっていて、パスワードが設定されていないという意味で、ログインできません。user の方は $y$j9T$rtHFK6THpa78Gy39URhRO/$HCih5lqJBNxKCKy/L7sWsT3z1phVm5AnN8BapO7rHW9 となっていて、ここを今回は深堀りしたいと思います。

簡単に言うと、この長い文字列にはハッシュ値が含まれていて、ユーザがログインするときに入力されたパスワードを同じ方式でハッシュ値を計算し、この値と一致すれば、ログインできるという仕組みになっています。

パスワードハッシュの詳細内容

user の $y$j9T$rtHFK6THpa78Gy39URhRO/$HCih5lqJBNxKCKy/L7sWsT3z1phVm5AnN8BapO7rHW9 を読み解きます。

最初の $y$ の部分は、ハッシュアルゴリズムの種別を表しています。代表的なアルゴリズムを並べておきます。

  • $1$:MD5
  • $2$:bcrypt(blowfish暗号)
  • $5$:SHA256
  • $6$:SHA512
  • $y$:yescryptアルゴリズム

次の $ の手前までの j9T はCPUコストの値というよく分からない値で、その次の $ の手前までの rtHFK6THpa78Gy39URhRO/ がソルト値と呼ばれるもので、$ の後ろから末尾までの HCih5lqJBNxKCKy/L7sWsT3z1phVm5AnN8BapO7rHW9 がハッシュ値になります。

ソルト値とは、入力に付加する値で、これにより、同じ入力でもハッシュ値が異なってきます。

ソルト値が無かった時のことを考えます。ハッキングしようとしたときに、膨大なパスワードリストを事前にハッシュ値を計算させておきます。そうすると、ハッシュ値を入手できると、事前に計算した値と一致するかどうかだけを見ていけばパスワードが分かってしまいます。

一方、ソルト値があると、ソルト値はランダムで長さも選べるので、膨大なパスワードリストに、このパターンを掛け算する量になり、事実上、事前の計算は不可能ということになるようです。

だいたい仕組みが分かったところで、これを具体的に計算していきたいと思います。

mkpasswdを使ってハッシュ値を生成する

Linux には mkpasswd というハッシュ値を生成するツールがあります。

対応しているアルゴリズムを見てみます。

$ mkpasswd --method=help
Available methods:
yescrypt        Yescrypt
gost-yescrypt   GOST Yescrypt
scrypt          scrypt
bcrypt          bcrypt
bcrypt-a        bcrypt (obsolete $2a$ version)
sha512crypt     SHA-512
sha256crypt     SHA-256
sunmd5          SunMD5
md5crypt        MD5
bsdicrypt       BSDI extended DES-based crypt(3)
descrypt        standard 56 bit DES-based crypt(3)
nt              NT-Hash

全部やってみたかったので、簡単なスクリプトを書きました。

対応していた全てのアルゴリズムについて、parrot というパスワードでハッシュ値を生成します。

ソルト値は指定しないと、ランダムに設定してくれるようです。

#!/bin/bash

set -e

for argo in yescrypt gost-yescrypt scrypt bcrypt bcrypt-a sha512crypt sha256crypt sunmd5 md5crypt bsdicrypt descrypt nt
do
    echo \# ${argo}
    mkpasswd --method=${argo} parrot
done

では、実行してみます。

$ ./mkpasswd.sh 
# yescrypt
$y$j9T$7LOOPn2x0uNgQgDZBldv71$JkYQAQK6iBmRCmiMKWEbjLNkHEWilODW5VSIXZ.GNaB
# gost-yescrypt
$gy$j9T$4qMwB2DKyI/9xp1b87Aa10$8hcBu/PV0.0Q7MWdVaKKPxmKLfushcdBN2BgAQTE171
# scrypt
$7$CU..../....sbW2KTc.VxnIb71VETaU2.$Q1CFLTUKuVszm58S6886Pej3M.GZWzRDhaMG.Dw9NH6
# bcrypt
$2b$05$aSLAqTaPE0kJ37VaLv96OuklOe9At7VTz52ETnCvlkdqS0XhEudDu
# bcrypt-a
$2a$05$8KKF99jUTJYUPioDj/og4e7eZoHh.CGyMefG0jh.IsSul9qs13NLa
# sha512crypt
$6$1DUzoB408sNa9Z6o$9Wq3q1TXfhmAT3ZWWJGZb9qhE0UEeDW1duvDPjaCeS0M3ooCSBlSnHjZsjMSeVYhElddhAqkJwB1z7GiOYqr51
# sha256crypt
$5$TA9ANtWJdoeG8zA/$GKir9NHqRckuJU.wXo6Ncm5ZRbxFG0trgwP2WrRBFB3
# sunmd5
$md5,rounds=68122$WuDvGxMb$$3.l6njnPqcTP6a1pV6iVq.
# md5crypt
$1$2U6gaAws$7M/XADDMURJrPyOsGJjYR0
# bsdicrypt
_J9..hsn4bKlsMxRcdbo
# descrypt
Xycb4n5QCfldM
# nt
$3$$f8cbe1ee8bb84088124a8bf57802f14f

$x$ のところは、いろいろあるようですね。

では、今回のソルト値とパスワードでハッシュ値を計算してみます。ソルト値は先頭からまとめて与えるようです。

$ mkpasswd --method=yescrypt --salt='$y$j9T$rtHFK6THpa78Gy39URhRO/$' parrot
$y$j9T$rtHFK6THpa78Gy39URhRO/$HCih5lqJBNxKCKy/L7sWsT3z1phVm5AnN8BapO7rHW9

無事に生成できました。

John the Ripperを使ってパスワードを解読する

John the Ripperを使ってみたいので概要を説明します

というわけで、参考書にも出てくるオフラインパスワードクラッカーの「John the Ripper」というツールを使ってみます。

まず、オンラインパスワードクラッカーとは、ログイン画面などに直接ユーザ名とパスワードを何度も入力してパスワードを解読することです(アクセスしたことが相手に分かる)。一方、オフラインパスワードクラッカーとは、ログイン画面に直接アクセスしないでパスワードを解読することです(アクセスしないので相手には分からない)。

John the Ripper の公式サイトは、以下です。ParrotOS には最初からインストールされています。

www.openwall.com

John the Ripper の仕組みを簡単に言うと、ハッシュ値とパスワードリストを入力すると、ハッシュ計算してくれて、総当たりでパスワードリストと照合して結果を出してくれるものです。

分かりやすく言いましたが、本当は、John the Ripper には、総当たりは最後の手段で、いくつかの付加情報をもとに、もっと効率的な方式を使ってくれます。また、ファジング(曖昧)と言って、パスワードリストにあるパスワードを一部変更して照合してくれる、などの機能があります。

John the Ripperを実際に使ってみる

早速やっていきます。

まず、John the Ripper はデフォルトの辞書を用意してくれています。そこに、ParrotOS のデフォルトのパスワード(parrot)が含まれているかを確認します。

$ grep 'parrot' /usr/share/john/password.lst
parrot

含まれていました。

では、John the Ripper を使って、先ほどの shadowファイルの内容を入力して、実行してみます。

わずか1分半でパスワードが解読されました。

下から4行目が解読されたパスワードと括弧内はユーザ名です。shadowファイルを与えたので、ユーザ名が分かるので、合わせて表示してくれるのだと思います。

$ sudo john /etc/shadow --format=crypt
Created directory: /root/.john
Using default input encoding: UTF-8
Loaded 1 password hash (crypt, generic crypt(3) [?/64])
Cost 1 (algorithm [1:descrypt 2:md5crypt 3:sunmd5 4:bcrypt 5:sha256crypt 6:sha512crypt]) is 0 for all loaded hashes
Cost 2 (algorithm specific iterations) is 1 for all loaded hashes
Will run 4 OpenMP threads
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
Almost done: Processing the remaining buffered candidate passwords, if any.
Warning: Only 4 candidates buffered for the current salt, minimum 96 needed for performance.
Proceeding with wordlist:/usr/share/john/password.lst
parrot           (user)     
1g 0:00:01:23 DONE 2/3 (2024-08-02 21:32) 0.01197g/s 56.43p/s 56.43c/s 56.43C/s national..rocket1
Use the "--show" option to display all of the cracked passwords reliably
Session completed. 

では、デフォルトのパスワードの parrot から、parrot2 に変更します。shadowファイルが変化しているかも確認します。

$ passwd
Changing password for user.
Current password:
New password:
Retype new password:
passwd: password updated successfully

$ sudo su
# cat /etc/shadow | grep user
user:$y$j9T$Bt7RsVb5ScDcaGIUWrTTd0$al5/C.22XHYflWckGH1El013Vvk0M067/ls0OPnvtG9:19937:0:99999:7:::

ソルト値、ハッシュ値ともに変化しています。

John the Ripper が用意してくれているパスワードリストに parrot2 が存在しているかを確認します。

$ grep 'parrot2' /usr/share/john/password.lst

parrot はありましたが、parrot2 は無かったです。

では、もう一度、John the Ripper を実行してみます。今度は先ほどより時間がかかります。

なお、出力メッセージ内にあるように、実行中に、q もしくは Ctrl-C で中断することが出来て、それ以外のキーを押すと現在の状況が確認できます。数分経過した後にリターンキーを押すと、7.88% と表示されました。30分ぐらいはかかりそうです。

と思ったら、7分半で解読できました。パスワードリストになくても、類似するパスワードは簡単に解読できるようです。

$ sudo john /etc/shadow --format=crypt
Using default input encoding: UTF-8
Loaded 1 password hash (crypt, generic crypt(3) [?/64])
Cost 1 (algorithm [1:descrypt 2:md5crypt 3:sunmd5 4:bcrypt 5:sha256crypt 6:sha512crypt]) is 0 for all loaded hashes
Cost 2 (algorithm specific iterations) is 1 for all loaded hashes
Will run 4 OpenMP threads
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
Almost done: Processing the remaining buffered candidate passwords, if any.
Warning: Only 4 candidates buffered for the current salt, minimum 96 needed for performance.
Proceeding with wordlist:/usr/share/john/password.lst
0g 0:00:03:00 7.88% 2/3 (ETA: 22:32:11) 0g/s 79.76p/s 79.76c/s 79.76C/s lorna1..peggy1
parrot2          (user)     
1g 0:00:07:36 DONE 2/3 (2024-08-02 22:01) 0.002189g/s 70.21p/s 70.21c/s 70.21C/s little2..patches2
Use the "--show" option to display all of the cracked passwords reliably
Session completed. 

パスワードは、少し変えれば大丈夫!ではないことが証明されましたね(笑)。

おわりに

今回は、Linux の shadowファイルについて理解を深めました。

パスワードの重要性がよく分かりました。

John the Ripper のロゴを使用させて頂きました、ありがとうございます。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。