ksnctf #9 Digest is secure!
前回はBasic認証でしたが、今回はDigest認証です。
Analyze
Wiresharkを用いて解析を行ってみます。
No.9で要求された認証に対してNo.14で認証情報を送信しています。
ここまではBasic認証のときと同じなのですが、Basic認証とDigest認証では送っている認証情報が異なります。
Digest認証
Basic認証では、認証に必要なユーザ名とパスワードを平文で送っていましたが、Digest認証ではそれらをMD5でハッシュ化して送っています。
更に、認証の際にはサーバ側から送られてくるnonce
を用いて認証情報を計算することによって同じユーザ名とパスワードでも毎回異なる値を送信することになります。
これによって、通信の盗聴や改竄、単純なリプレイ攻撃にも耐性があるのではないかと思われます。
ここで重要になってくるのは、実際に送信する認証情報の構造です。
認証情報として送信するresponse
は以下の通りです。
A1
=username
:realm
:password
A2
=HTTPのメソッド
:コンテンツのURI
response
= MD5( MD5(A1
) :nonce
:nc
:cnonce
:qop
: MD5(A2
) )
No.7で送られてきたnonce
を元にしてresponse
を計算し、No.14のように認証情報をサーバへ送ることによって認証を通過できると思います。
Answer
まずnonce
以外のnc
やcnonce
を求めるために、No.14のパケットからresponse
を取り出してみると、c3077454ecf09ecef1d6c1201038cfaf
となっていました。
ハッシュ関数として用いられているMD5は復号できる場合があるので、適当にググってみると以下のサイトで復号できました。
MD5変換(MD5),MD5逆変換(MD5Reverse)
復号前:c3077454ecf09ecef1d6c1201038cfaf
復号後:c627e19450db746b739f41b64097d449:bbKtsfbABAA=5dad3cce7a7dd2c3335c9b400a19d6ad02df299b:00000001:9691c249745d94fc:auth:31e101310bc
つまり、このようになります。*1
MD5(A1 ) |
c627e19450db746b739f41b64097d449 |
---|---|
nc |
00000001 |
cnonce |
9691c249745d94fc |
qop |
auth |
MD5(A2 ) |
31e101310bc |
これらを利用して、認証情報を作成してサーバに送ってやるとFLAGをゲットできました。
#!/usr/bin/env python # coding: UTF-8 import urllib import urllib2 import md5 url = 'http://ksnctf.sweetduet.info:10080/~q9/flag.html' username = "q9" realm = "secret" nonce = "" uri = "/~q9/flag.html" algorithm = "MD5" response = "" qop = "auth" nc = "00000001" cnonce = "9691c249745d94fc" md5a1 = "c627e19450db746b739f41b64097d449" a2 = 'GET:' + uri def getNonce(): try: data = urllib2.urlopen(url) html = data.read() except urllib2.HTTPError, e: nonce = e.info()['WWW-Authenticate'].split('"')[3] return nonce def genMD5(str): return md5.new(str).hexdigest() def genResponse(nonce): response = genMD5(md5a1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + genMD5(a2)) return response def genAuthorized(nonce, response): authorized = 'Digest username="' + username + '", realm="' + realm + '", nonce="' + nonce + '",uri="' + uri + '", algorithm=' + algorithm + ', response="' + response + '", qop=' + qop + ', nc=' + nc + ', cnonce="' + cnonce + '"' return authorized def main(): nonce = getNonce() response = genResponse(nonce) auth = genAuthorized(nonce, response) header = {'Authorization' : auth} req = urllib2.Request(url, None, header) try: data = urllib2.urlopen(req) html = data.read() print html except urllib2.HTTPError, e: print e.code print e.info() if __name__ == '__main__': main()
*1:ここで得たnonceは一時的に生成されたものなので認証情報を作成する上では利用できません。