天才クールスレンダー美少女になりたい

チラシの表(なぜなら私はチラシの表にも印刷の上からメモを書くため)

zer0pts CTF 2022 writeup

沖縄——本土から海と島々で隔てられた先にある、南の海に浮かぶ島。
その島を走る路線バスに揺られながら、私は溜息をついた。

路線バスは好きだが、得意ではない。すぐ酔ってしまうから。





逆張り北国𝑳𝒐𝒗𝒆オタク——私を含む——は概して大人気の南国を避けがちだが、しかし南西諸島はオタクにとっても天国だ。
海、離島、港町、路線バス、美味しいご飯。嫌いなオタクがいるとは到底思えない。

もっとも、今回の沖縄行きは家族旅行なるものであって、自分で計画したものではないのだが。逆張りオタクの自然な発想で出てくる旅行先ではないので。




名護から本部(もとぶ)半島を一周するバスに乗り、今帰仁(なきじん)(グスク)跡を訪問。地元出身のガイドさんに案内してもらう(なんと無料!)。

こういう場所は、やはり隣接する資料館が面白い。次の予定の都合で時間があまり取れなかったのが残念だった。あと、今の眼鏡を電車寝落ちで破壊したせいで弱い眼鏡で代用してて説明文がまともに読めなかったことも。(これはお前がバカなだけ)


そのまま同じバスを同じ向きに乗り、今度は美ら海水族館へ。微妙に値段が高い食事処で黒糖いなり寿司と沖縄鶏の唐揚げを食した後(量の割に高い!)、いよいよ入場。日本有数の水族館と聞いており、期待が高まる。

何よりも楽しみなのは、この日開催されるzer0pts CTF 2022だ。
正直に言うと私はCTFが全くできないのだが——下手の横好きというやつだ——、zer0ptsのメンバーである特定のオタクと地味に古いFFだったり、そのオタクのアイコンを描いた絵師が私の同人仲間だったり(アッ夏コミの進捗がヤバい!)、なんか謎の関係性があり、ある。出ないわけにはいかない。




ところで、私が所属している東京大学のTSGなるパソコンカタカタサークルは、CTFの界隈でも有名らしい。確かにTSGのCTFer各位は大変強く、強いのだが、いかんせん私は弱い。CTFは楽しいし、強くなりたいという願望もあるが、成長が全く追い付いていないのが現状。サークルからCTFの灯を絶やさないためにも、強くなりたいね……*1



とりあえず、warmup問はTSGの誇る最強CTFerたちが教育的配慮で残してくれていた。webしか解けないのにwebも解けない最弱CTFerとしては「スパシーバ〜」と言いながら取り組むしかない。いやマジでwebって総合格闘技みたいなとこあるし、cryptoやmiscの要素が強いと徳丸本を読んだ程度の私では手が出ないがち。まあ普通に「純粋web問」も全然解けないんですけど


休憩用の椅子に座ってパソコンを開き(迷惑客か?)、水族館のフリーWi-Fiに接続し、いざ尋常に勝負。

GitFile Explorer

人々がコードリーディングして本質部分を抜き出してくれていたので、ぐっと睨む。

<?php
// ...
$url = craft_url($service, $owner, $repo, $branch, $file);
if (preg_match("/^http.+\/\/.*(github|gitlab|bitbucket)/m", $url) === 1) {
    $result = file_get_contents($url);
}

$urlをいい感じのパスにしてディレクトリトラバーサりたいが、正規表現が邪魔という感じらしい。

正規表現を睨むと、こいつがスキームの後のコロンを要求していないことが分かる。そこで、$urlhttps//github/../../flag.txtみたいな感じにすると、たぶんディレクトリトラバーサルが発生してフラグが読めるはず。スラッシュ2つは1つと同じ扱いだったはずだし。


そこまで書いたところで家族に呼ばれたので、あえなく離脱。フラグゲットだけ人に任せるアレな人間が発生してしまった。ごめん……

http://gitfile.ctf.zer0pts.com:8001/?service=http/..//github&owner=../../../../&repo=..&branch=..&file=flag.txt

zer0pts{foo/bar/../../../../../directory/traversal}


まあ、warmupなので秒殺です。いや秒殺は嘘だしそもそもソースコード読解フェーズを人任せにした時点で秒殺とか言う権利ない。







水族館の中で特に印象的だったのは、水族館の研究者たちが最先端の生態学的研究に貢献しているという話だった。水槽を眺めていても「ほーん」としか思わない感受性死滅人間こと私、学問に携わる人々の営みを目の当たりにして思わず号泣。や、さすがにマジで泣いてはいないが……




Zer0TP

ホテルに戻り、残りの問題を見てみる。他の問題が何やってるのか一切分からず死亡し、消去法でこの問題に。

# /api/login
id = os.urandom(8).hex()
r = redis.Redis(host=REDIS_HOST, port=6379, db=1)
secret = r.get(username)
if secret is None:
    secret = base64.b64encode(os.urandom(12))
    r.set(username, secret)
    r.expire(username, 60*30)

token = zlib.compress(username + secret)[:8]
return flask.jsonify({"result": "OK", "id": id, "token": 
    hashlib.md5(id.encode() + token).hexdigest()})

web……webかあ……これがweb……いやまあmiscでもあると明記されてるから主催者さん側に罪は一切ないけども……


secretが分かればadminに成り代わってflagを奪取できる。なので、レスポンスからsecretを復元したい……のだが、んなもんできたら苦労しなくないか。いくらmd5がやや危殆化してるからといって、別にハッシュ値から元のデータを復元できるわけじゃないし……


まあ、仮にレスポンスからtokenを復元できたと仮定しよう。できるか知らんけど。そうすると、usernameはこちらで指定できるから、zlibで圧縮したやつの先頭8バイトからsecretの先頭だけ復元できる……気がする。
8バイトを32bitだと思ってギリ全探索できるかもとか世迷い言を口走ったのは秘密。ビットとバイトの換算すらできん


そこでzlibの仕様をグッと睨んだり実際にzlibで圧縮してみたデータ列をバイナリエディタで眺めてみたりしたところ、これくらいの長さのバイト列なら本体データは無圧縮か既定のハフマン符号で表現されるかの二択っぽいことが分かる。そうすると先頭2〜3バイトくらいは復元できるのかな? usernameを1文字とかにすれば……いや先頭2,3文字しか分からないじゃん、無理では??????? どう足掻いても後ろの情報が前に来ることなくない?



ここでプロCTFer(具体的にはkcz氏)がrenameを駆使してmd5をなんかうまいことやる方法を思いついたとか言っていて、結局先頭2バイトだけを当てる実装はできたと聞いている(伝聞)。「usernameを長くしてレスポンスを見る、次にusernameを短くしてレスポンスを見る、次はさらに短くして……」みたいなことをやるっぽいのだが、具体的にどうするか私には見当もつかない。




keymoon.hatenablog.com

ホ〜〜〜〜〜(なんもわからん 頑張って咀嚼します)





次の日は大石林山に行って、夕方に名護から那覇に戻った。
カルスト地形とガジュマルが作り出す壮大な自然の中を歩きつつ、解けない問題のことを、そして自分があまりに弱いということを頭の片隅でずっと考え続けていた。



感想

st98さんのweb問が欲しい人生だった……いやまあ、st98さん問なら解けるとかそんな馬鹿なことを言うつもりはないけど。Q. WebしかできないのにJavaScript問すらまともに解けないやつ、だ〜れだ? A. ふぁぼん
だいたい、そもそも私が純粋培養web解きなのが致命的というだけの話だし。zer0ptsのみなさんが悪いわけでは全くない。


今までパソコンいじりやweb開発で得た知識——Linuxや各種フレームワーク脆弱性の知識、JavaScriptの謎言語仕様など——を動員して問題を解くのが楽しいのは確かだけど、そこから外れたところも勉強するべきなんだろうなあ……


それと、CTFは競プロよりだいぶ楽しめているしモチベもあるけど、さりとて努力できないという自分の弱さを克服するまでには至っていない。カスと怠惰のクオリディアが代……



あ、でも中2か中3くらいのときに初めて徳丸本読んでワクワクしたときの気持ちは思い出せたかも。初心忘るべからず。

*1:強い人はだいたい卒業生か院生で、大学チームとしては割と珍しく残ってはいるけど世代交代が上手くいったわけじゃない感はある。いや、こういうこと言うと同年代のCTFをやる部員に失礼だな……弱いのは私だけのため……というか私も3年生だし既に老人か?