前回 は、Flask で実装した Pythonスクリプトを Cython化してみました。
今回は、参考書にしている「ハッキング・ラボのつくりかた 完全版 仮想環境におけるハッカー体験学習」で解説されていた shadowファイルについて、実際の shadowファイルを使って、内容を理解し、解析までやってみたいと思います。
それでは、やっていきます。
参考文献
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
セキュリティの記事一覧
開発環境は、VirtualBox + ParrotOS 6.1 です。
今回のシリーズで参考書としている「ハッキング・ラボのつくりかた 完全版 仮想環境におけるハッカー体験学習」ですが、とても良い本です。
約1180ページあるので、見た目は辞書ですし、持ち歩くのは大変で、寝る前に布団に入って読むのも難しいです(笑)。
ハッキングの解説が主な内容のはずなんですが、ハッキングのための事前知識の説明に、多くのページを割いています。感覚的に半分ぐらいはハッキングの事前知識かもしれません。
この事前知識とは、Linux の基本的な(膨大な)知識であり、本来は非常に難解な内容のはずですが、とても分かりやすく説明してくれています。
私の場合、Linux は長い間使ってきたので、だいたい分かってた気になってましたが、この本を読み進めてると、まだまだ知る必要のある内容は多いなと感じました。
あまり、つぶやかないタイプだと思います(日本語が下手なので)が、たまには思ったことも書くのもいいな、と思いました。
では、今回もやっていきます。
Linuxのユーザ名とパスワードの管理方法
Linux では、ユーザがログインするときに入力されるユーザ名とパスワードの認証を、以下のファイルで管理しています。
それぞれを読んでみます(長いので一部だけ貼ります)。shadowファイルの方は、読むのに root権限が必要なので、root に切り替えておきます。
具体的にログインするのは root と user だけで、それ以外はデーモンで動作しているシステムユーザでログインは出来ないようになっています(nologin となっている)。
$ sudo su
-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
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
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
というパスワードでハッシュ値を生成します。
ソルト値は指定しないと、ランダムに設定してくれるようです。
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
$y$j9T$7LOOPn2x0uNgQgDZBldv71$JkYQAQK6iBmRCmiMKWEbjLNkHEWilODW5VSIXZ.GNaB
$gy$j9T$4qMwB2DKyI/9xp1b87Aa10$8hcBu/PV0.0Q7MWdVaKKPxmKLfushcdBN2BgAQTE171
$7$CU..../....sbW2KTc.VxnIb71VETaU2.$Q1CFLTUKuVszm58S6886Pej3M.GZWzRDhaMG.Dw9NH6
$2b$05$aSLAqTaPE0kJ37VaLv96OuklOe9At7VTz52ETnCvlkdqS0XhEudDu
$2a$05$8KKF99jUTJYUPioDj/og4e7eZoHh.CGyMefG0jh.IsSul9qs13NLa
$6$1DUzoB408sNa9Z6o$9Wq3q1TXfhmAT3ZWWJGZb9qhE0UEeDW1duvDPjaCeS0M3ooCSBlSnHjZsjMSeVYhElddhAqkJwB1z7GiOYqr51
$5$TA9ANtWJdoeG8zA/$GKir9NHqRckuJU.wXo6Ncm5ZRbxFG0trgwP2WrRBFB3
$md5,rounds=68122$WuDvGxMb$$3.l6njnPqcTP6a1pV6iVq.
$1$2U6gaAws$7M/XADDMURJrPyOsGJjYR0
_J9..hsn4bKlsMxRcdbo
Xycb4n5QCfldM
$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
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 のロゴを使用させて頂きました、ありがとうございます。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。