SHA2017 CTF round Teaser Website Attack writeup

SHA2017 CTF round Teaser Website Attack (Network) のwriteup。ひさしぶりにCTFをやった。

問題文

Our website received an attack in 2013, we managed to capture the attack in this pcap. Can you find out if we leaked some sensitive information?

website-attack.pcapが与えられる。

decode as

IPAという見慣れないプロトコルのパケットがあるが、中身を見ると明らかにHTTPなのでwiresharkDecode As...でHTTPとして認識させる。

f:id:TAKEmaru:20170621031102p:plain

通信内容

WiresharkExport Objects → HTTPでHTTPのリクエストをざっと見てみる。

f:id:TAKEmaru:20170621045208p:plain

action=search&words=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&sort=stockの後のリクエストはwordsパラメータが少しずつ変化しているのでBlind SQL Injectionっぽい通信だなと分かる。

通信の流れ

  1. GET /
  2. GET bootstrap.min.css
  3. GET /?action=search&words=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&sort=stock
  4. redirect GET /?action=display&what=e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a74799edb6fda5b44
  5. GET /?action=display&what=af7d6f4240be9a2d31252290ef5b7e797dd7fc3be66d6d6766b5375a79b84d428964052355a9f53759403fe18b416f7067d9e948e17d7d147eae52605cf4505f947c0c3e33dc8a5d593424f58928484157f7c33bf0747c7112976d406bb14136eb1105
  6. 5のwhatパラメータを少しずつ変えながらGETリクエストを投げ続けている。

Our website received an attack in 2013?

2013年にRC4に対する脆弱性(JVNDB-2013-001910 - TLS プロトコルおよび SSL プロトコルで使用される RC4 アルゴリズムにおける平文回復攻撃を実行される脆弱性)が発表されていた。ので、RC4が関係していると考える。

RC4 , Is it possible to find the key if we know the plaintext and ciphertext? - Cryptography Stack Exchangeによると、RC4のciphertext Cはplaintext PとkeyであるRC4(key)のXORで求められる。

f:id:TAKEmaru:20170621163826p:plain

よって、RC4(key)が同一であった場合、Pは2つ目のplaintext P'、2つ目のciphertext C'を用いて以下のように表せる。

f:id:TAKEmaru:20170621031035p:plain

これを用いてリクエスト内容を復号化する。このような式になるのはRC4がXORを用いて暗号化しているからだ。

これ以外の2013年のメジャーな攻撃にはTLSに対するCBCモードPadding oracle attackであるLucky Thirteen Attackがあったが今回はTLSに関係ない。

解く

RC4のものと思われるchipertextを、Wiresharkでexportしたファイル名から取り出し、sql文にdecryptする。

コード

# coding: UTF-8

import os

files = os.listdir('./exports')


def decrypt(encrypted):
    p2 = ""
    for i in range(len(encrypted) / 64 + 1):
        c = encrypted[i * 64: (i + 1) * 64]
        c2 = "e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032"
        p = "4141414141414141414141414141414141414141414141414141414141414141"
        for j in range(len(c) / 2):
            begin = j * 2
            end = (j + 1) * 2
            c_i = ord(c[begin: end].decode('hex'))
            c2_i = ord(c2[begin: end].decode('hex'))
            p_i = ord(p[begin: end].decode('hex'))
            p2 = p2 + chr(c_i ^ c2_i ^ p_i)

    return p2

for filename in files:
    with open('./exports/' + filename, 'r') as f:
        data = f.read()
        # レスポンスが HTTP/1.1 200 OKのときだけ
        if data.find('hyper') < data.find('Traditional'):
            print decrypt(filename[23:])

実行結果

flagテーブルからSELECTしているSQL文とSQLテーブルからSELECTしているSQL文が混在している。

$ python solve.py
...
(CASE WHEN (SELECT SUBSTR(flag,28,1)  FROM secret_flag LIMIT 0,1) = '1' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(flag,2,1)  FROM secret_flag LIMIT 0,1) = 'l' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(flag,9,1)  FROM secret_flag LIMIT 0,1) = '7' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(flag,8,1)  FROM secret_flag LIMIT 0,1) = '0' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(sql,3,1) FROM  SQLITE_MASTER LIMIT 1,1) = 'E' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(sql,35,1) FROM  SQLITE_MASTER LIMIT 1,1) = 't' THEN stock ELSE price END

(CASE WHEN (SELECT SUBSTR(sql,34,1) FROM  SQLITE_MASTER LIMIT 1,1) = 'x' THEN stock ELSE price END
...

この結果を踏まえて、flagテーブルを操作しているSQL文から一文字ずつflagを抜くことを考える。

コード

# coding: UTF-8

import os

files = os.listdir('./exports')


def decrypt(encrypted):
    p2 = ""
    for i in range(len(encrypted) / 64 + 1):
        c = encrypted[i * 64: (i + 1) * 64]
        c2 = "e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032"
        p = "4141414141414141414141414141414141414141414141414141414141414141"
        for j in range(len(c) / 2):
            begin = j * 2
            end = (j + 1) * 2
            c_i = ord(c[begin: end].decode('hex'))
            c2_i = ord(c2[begin: end].decode('hex'))
            p_i = ord(p[begin: end].decode('hex'))
            p2 = p2 + chr(c_i ^ c2_i ^ p_i)

    return p2

flag = [' ' for _ in range(40)]

for filename in files:
    with open('./exports/' + filename, 'r') as f:
        data = f.read()
        # レスポンスが HTTP/1.1 200 OKのときだけ
        if data.find('hyper') < data.find('Traditional'):

            for filename in files:
                with open('./exports/' + filename, 'r') as f:
                    fc = f.read()
                    if fc.find('hyper') < fc.find('Traditional'):
                        decrypted = decrypt(filename[23:])

                        if decrypted.find('flag') != -1:
                            flag_i = decrypted.find("'") + 1
                            if flag_i != 0:
                                flag_c = decrypted[flag_i]

                            substr_position_i = decrypted.find(',') + 1
                            if substr_position_i != 0:
                                substr_position = decrypted[substr_position_i:substr_position_i + 2]
                                if substr_position[1] == ',':
                                    substr_position = substr_position[0]

                            if flag_i != 0:
                                flag[int(substr_position)] = flag_c


print ''.join(flag)

実行結果

$ python solve.py
 flag{7307e3ee8da198ca4a7f9b1f8b018d8e} 

参考資料