かたはらいたし。

個人的な技術的な活動をまとめていきたい。

ksnctf #9 Digest is secure!

前回はBasic認証でしたが、今回はDigest認証です。

ksnctf - 9 Digest is secure!

Problem

http://ksnctf.sweetduet.info/q/9/q9.pcap

前回と同様pcapファイルのみがあります。

Analyze

Wiresharkを用いて解析を行ってみます。
f:id:tmsk8219:20160705135800p:plain

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以外のnccnonceを求めるために、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()

感想とか

Digest認証はBasic認証より安全というくらいの認識しかなかったので、とても勉強になった。
前回のBasic認証の問題を発展させた良い問題だったと思います。

*1:ここで得たnonceは一時的に生成されたものなので認証情報を作成する上では利用できません。