BASIC認証とDigest認証、hydraによる辞書攻撃

某所でajitingさせてもらうという貴重なユーザ体験をしたのですが、「THC-Hydraでhttp-getにてクラックするとき、BASIC認証とDigest認証を自動判別してくれるのか?」という質問に答えられずに悔しい思いをしました。ので、ちゃんと検証しました。

結論から言うと、THC-Hydraは自動でBASIC認証/Digest認証を判断してくれるので、http-get指定だけでよく、特別に設定する必要はありません。

BASIC認証のおさらい

まずはじめに、BASIC認証のおさらいをしておきます。

BASIC認証では、IDとパスワードをコロンで連結した文字列を、Base64エンコードしてAuthorization:ヘッダに付けることで認証をおこないます。

GET /basicauth/ HTTP/1.1
Host: 192.168.2.66
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Authorization: Basic Z3Vlc3Q6VGFrZTU=
Connection: keep-alive
Cache-Control: max-age=0

上の例は、BASIC認証配下にFirefoxでアクセスした際のリクエストヘッダですが、このうち以下の部分がBASIC認証のヘッダとなります。

Authorization: Basic Z3Vlc3Q6VGFrZTU=

この文字列は、以下のようにbase64コマンドでデコードすればそのままIDパスワードが得られます。そのため、BASIC認証は全く暗号化されていない……というのはまぁ初級教科書レベルのお話ですね。

$ echo Z3Vlc3Q6VGFrZTU= | base64 -d
guest:Take5

本稿では、この通りにIDはguest、パスワードはTake5で例を進めます。

Digest認証

BASIC認証では平文でIDパスワードが流れるので、これを嫌って、パスワード(+α)からメッセージダイジェストを生成してそれをやりとりするのがDigest認証です。

Digest認証は、ユーザからするとほとんど違いが分かりません。たとえばFirefoxでは下図のように、Digest認証であっても見た目はBASIC認証と同じ、IDパスワードの入力ダイアログが出ます。ちなみにIEでは見た目がちょっと違います(BASIC認証の方は、危険だよ、って出してくれます)。

Digest認証について細かい話をしているとそれだけでこの記事が終わってしまうので、以下、エッセンスだけ書きます。詳しくはRFC2617を読みましょう。

Digest認証では、サーバから送られてきたランダム文字列(nonce)とパスワードのメッセージダイジェストを取り、その値をサーバ・クライアント間でやりとりします。そのため通信路にパスワードが平文で流れず安全です。また、リクエストの都度nonceが変わるので、同一パスワードのリクエストでもメッセージダイジェストは異なり、リプレイ攻撃もおこなえません(と書きたいのだが、Webサーバの実装によっては既に認証したnonceを再送されたときに受け取っちゃうことが……ややこしいのでここは今は踏み込みません)。

では、Digest認証のヘッダを見てみましょう。まずは最初のリクエストに対する、サーバからのレスポンスヘッダを見ます。

HTTP/1.1 401 Authorization Required
Date: Thu, 04 Sep 2014 16:15:48 GMT
Server: Apache/2.2.15 (CentOS)
WWW-Authenticate: Digest realm="Digest Auth", nonce="aHZlqj8CBQA=e444ef7072d5cc9e9682e0aeea334f8454d92f9c", algorithm=MD5, qop="auth"
Content-Length: 479
Connection: close
Content-Type: text/html; charset=iso-8859-1

Digest認証配下にGETリクエストを投げると、上のようなHTTP 401レスポンスが返ります。WWW-Authenticateというレスポンスヘッダがあり、この内容を元に再度認証リクエストを投げることが期待されています。

Firefoxで、このレスポンスに対してIDパスワードを入力した際のリクエストヘッダは以下です。

GET /digestauth/ HTTP/1.1
Host: 192.168.2.66
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Authorization: Digest username="guest", realm="Digest Auth", nonce="aHZlqj8CBQA=e444ef7072d5cc9e9682e0aeea334f8454d92f9c", uri="/digestauth/", algorithm=MD5, response="a39420f8ca7ca5587083e4e01f9a082d", qop=auth, nc=00000001, cnonce="a864d6a0e55de5d8"

見て分かる通り、Authorization:ヘッダはBASIC認証に比べてとても複雑です。とりあえず重要なのは、username,realm,responseの3つです。

usernameは、ログインする際のユーザ名で、ダイアログボックスに入力した値です。見て分かる通り、Digest認証ではユーザ名は平文で流れるため、ユーザ名自体も隠したい場合にはDigest認証は意味がありません。

realmは認証領域という意味で、Webブラウザの実装としては同じrealmにはID/パスワード再入力を求めない、というのが普通です。BASIC認証ではこのrealmはかなりテキトーでもなんとかなりましたが、後述するとおりDigest認証ではこのrealmを意識した設定をしないと正しく動きません。

responseが、「色々やってメッセージダイジェストを取った値」です。色々するのが大変なのでここでは解説を省略しますが、基本的にはnonceとパスワードをこねくり回してMD5を取る、イメージです。サーバ側でも同様にMD5を取り、それが一致すれば認証OKということになります。

ApacheでのDigest認証の設定

ApacheでDigest認証を利用したい場合、BASIC認証の場合と設定は似ていますが、若干違う部分もあってハマることがあるのでまとめておきます。

まず、パスワードファイルはhtdigestコマンドを利用して作成します。htpasswdコマンドと違って、オプションは一つしかないので簡単です。引数を何も付けずに実行すると、ヘルプが出ます。

$ htdigest
Usage: htdigest [-c] passwordfile realm username
The -c flag creates a new file.

Digest認証では、realmの値が必須です。というわけでユーザ名guestのパスワードファイルは以下のように作れます。

$ htdigest -c .htpasswd "Digest Auth" guest
Adding password for guest in realm Digest Auth.
New password:
Re-type new password:
$ cat .htpasswd
guest:Digest Auth:0444a04e4907251201e5dd43ebc79721

見て分かるように、できあがったパスワードファイルには、realmの値も一緒に含まれています。

一方、Apache側のconfファイルは以下のようになります。

AuthType Digest
AuthName "Digest Auth"
AuthUserFile /hoge/fuga/.htpasswd
Require valid-user

AuthTypeはBASICではなくDigestになります(当たり前か)。そしてAuthNameは、realmと必ず一致させます。それ意外は、BASIC認証と同様です。

THC-HydraはDigest認証もOKか

ようやく本題です。試してみた結論から言うと、クラックツールTHC-Hydra(私が試したのはhydra v7.6)では、はじめに決め打ちでBASIC認証を投げますが、サーバ側からDigest認証の401レスポンス(WWW-Authenticate: Digest)が返ると、自動的にDigest認証の試行に切り替えます。そのため、相手がBASIC認証だろうがDigest認証だろうが気にする必要はありません。

この辺の動きを確認するため、Apacheアクセスログファイルを、「Authorization:ヘッダのみ出す」という変態設定に変えておきましょう。

LogFormat "%{Authorization}i" basic-auth
CustomLog logs/access_log basic-auth

Apacheでは、%{}iでくくると、そのヘッダだけをアクセスログに出すことができます。上記のようにすると、Authorizationヘッダだけがアクセスログに出ますから、クラックツールをかけながらログを見ればBASIC認証で来ているのかDigest認証で来ているのか、すぐ分かります。

(追記:この設定方法は、@k_morihisaさんに教えていただきました、多謝)

hydra実行後

この状態で、Digest認証のURLに対して、hydraにより辞書攻撃をおこないます。

# hydra -l guest -P password.txt -V 192.168.2.66 http-get /digestauth/

この際のアクセスログは、以下のようになっていました。

Basic Z3Vlc3Q6MTIzNDU2Nzg5MA==
Basic Z3Vlc3Q6MTIzNDU2Nzg5
Basic Z3Vlc3Q6cGFzc3dvcmQx
Basic Z3Vlc3Q6cGFzc3dvcmQ=
Basic Z3Vlc3Q6YWJjMTIz
Basic Z3Vlc3Q6Y29tcHV0ZXI=
Basic Z3Vlc3Q6dGlnZ2Vy
Basic Z3Vlc3Q6MTIzNA==
Basic Z3Vlc3Q6bW9uZXk=
Basic Z3Vlc3Q6cXdlcnR5
Basic Z3Vlc3Q6Y2FybWVu
Basic Z3Vlc3Q6c2VjcmV0
Basic Z3Vlc3Q6bWlja2V5
Basic Z3Vlc3Q6MTIzNDU=
Basic Z3Vlc3Q6MTIzNDU2
Digest username=\"guest\", realm=\"Digest Auth\", response=\"dcfd2a29a6edb44e9068fee80ee6156e\", nonce=\"Y9dTRkACBQA=a1dbb2f8acaa9bfa446f6e18ee020f33a2bf9655\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"04ba95394694350564be125bf3389313\", nonce=\"XdtTRkACBQA=bc354fdcb418a953351f44c555785861c1a2f253\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"6dcc9300dd8909879dfd901be46399e4\", nonce=\"r91TRkACBQA=a809b2c17031ef6e22d64f3204a5ed47ad60dd77\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"0784a6a4ad1804bd4d89c7ecd0c871c6\", nonce=\"5d9TRkACBQA=62da8bb56be017fd308842850ec8e0d73af7ad9a\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"afdd034111aae96aaa613cfb965cc9d3\", nonce=\"GOJTRkACBQA=855944124c584e24141089f2cc61aaad87fd8cde\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"52ea689f363877bc8420db0eee1d9bf0\", nonce=\"TuZTRkACBQA=2225cd3596754b51a0916750486104871fa994a0\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"49e1a158df392b9374b8f99a25cc4605\", nonce=\"XeRTRkACBQA=7342f2245352aae8977567d9d9144ca92317399f\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"076b5c2a3139dccb24407f6cfd8ac625\", nonce=\"XudTRkACBQA=e70ef746e3825e49fac5369187d30331ad05e91a\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"ac602f205bafd624e7705f86549b4803\", nonce=\"O/RTRkACBQA=047062adc25ab28ebc70a626426dc587554a8eb7\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"10229f28e99514f55bd7919268ca05df\", nonce=\"EfVTRkACBQA=1328c1f6f464dc04d99660705779fe7bc752fbcc\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"b09269eda3abc9599858dda58c795054\", nonce=\"h/ZTRkACBQA=2d0b56eabafc316457a08fd99b70ba576d1f3970\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Basic Z3Vlc3Q6MTIzNDU2Nzg=
Digest username=\"guest\", realm=\"Digest Auth\", response=\"8f1d570198d7776107690309b6271d95\", nonce=\"oPdTRkACBQA=edc7a5b09a5c0d21f48bd1b5aaab5d674e496376\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
....(省略)

BASIC認証によるリクエストが、数えてみると16個あることに気がつきましたでしょうか(ちなみに、BASIC認証によるリクエストが1つだけずっと下の方にあるのは、これだけ何らかの理由で応答が遅れたからと思われます。Apacheでは、アクセスログはリクエストが来た時ではなくリクエスト完了時に出力されるので、応答が遅れるとログ出力も遅れます)。

THC-Hydraでは、デフォルトでは1ホストに対して並列で16タスクが実行されます。このため動きとしては、まず16個のBASIC認証のリクエストをドバッっと投げて、その応答を見て「あ、やべ、こいつDigest認証だ」と気がついて、それ以降はDigest認証で試す……という流れになっています。

これは、例えば以下のように並行実行のタスク数を-tオプションで1と設定すれば、BASIC認証は最初に1回だけ投げて、その後はすべてDigest認証になるという動きをするため確認できます。

# hydra -l guest -P password.txt -t 1 -V 192.168.2.66 http-get /digestauth/

上記のようにタスク1で実行した場合は、確かに以下のようにBasic認証ははじめの1回だけで、あとはすべてDigest認証で投げていました。

Basic Z3Vlc3Q6cGFzc3dvcmQ=
Digest username=\"guest\", realm=\"Digest Auth\", response=\"a78ccd064d27c938e00d042d0fe9d5ae\", nonce=\"LaieT0ACBQA=61b0dfbe861326d9786ba0383d25885575bcd955\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"0e1ad3e8b8552ef3ddf65809a2190e27\", nonce=\"prCeT0ACBQA=2ff7d2c953b2a4085f199b09a6d1c9199ee4cea2\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"7194740eb29bc944d08a6a8c4e8f1d60\", nonce=\"ZbaeT0ACBQA=2d931d88946abe6d12edda34b0ffa9a81fc706e7\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"9372995a6b6391edb172aafb5cc54e0c\", nonce=\"ObyeT0ACBQA=ec7034b50592fe03fbe8aa3a33733c7c58eaed35\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"428519b1ee149f51d385927181e03d1c\", nonce=\"IcKeT0ACBQA=d94a79ded08892b9d28d2a064cf39f02ddebf198\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"
Digest username=\"guest\", realm=\"Digest Auth\", response=\"62db0ee2d76de0b7893f9c9b4f5da8d7\", nonce=\"T8eeT0ACBQA=fc0c6738712fd26fae07b1865da25011f03e99c1\", cnonce=\"hydra\", nc=00000001, algorithm=MD5, qop=auth, uri=\"/digestauth/\"

というわけで、hydra安心。