(decrypto's signature cookie is important.. the text at the top is wrong. Sorry!) Hack the mainframe! Location -
Web +Crypto。幸いにも問題情報がgithubで公開されてた。丁寧。dockerを建ててアクセスしてみると次のように言われる。
We've logged into the mainframe, but we have a crappy UID! Can you set your UID to 0 instead?
It might interest you to know that the signature cookie is SHA256(8-byte secret || data). But I've already said too much!
Note: the "rack.session" cookie is not part of the challenge!
それから、ページにアクセスすると文字が流れる様になっているんだけど、そちらはこんな感じのJS実装で残念ながら特に見るべきところはない。
var data = [] data.push("Welcome to the mainframe!"); data.push("It looks like you want to access the flag!"); data.push("Please present user object"); data.push("...scanning"); data.push("...scanning"); data.push("Scanning user object..."); data.push("...your UID value is set to 40"); data.push("...your NAME value is set to baseuser"); data.push("...your SKILLS value is set to n/a"); data.push("ERROR: ACCESS DENIED"); data.push("UID MUST BE '0'"); var current = data.shift(); function t() { return Math.random() * 40 + 40; } function append() { if(current.length == 0) { document.getElementById('content').innerHTML += "\n"; if(data.length == 0) { return; } if(Math.random() < 0.5) { current = "..."; } else { current = data.shift(); } } c = current[0]; current = current.substr(1); document.getElementById('content').innerHTML += c; setTimeout(append, t()); } setTimeout(append, t());
cookieを見てみると、 rack.session, signature, userという3つが定義されている。この内 rack.session は関係ないらしいので考えるのはsignatureとuserのみ。signatureはsha256(salt || data) になっているらしい。実際のデータは↓
uid = 40, username = baseuser, skills = n/a のとき
signature=d84d04a318ae83b1eb82225df54a4c21fd74b6300e47a485c8f26a1b587b4a65
user=32a1cab33801beb72d9bc8a6ecd0ec1aa60a26910003c8803cb3f619fd0e428e6b5c68723502ca8777a689e69c7835c4ce7d48166e8afd8dfb6a809b0260e6d7
uid = 48, username = baseuser, skills = n/aのとき
signature=ce4b1ccb5ee28ad81d38bc89c8526f46c2d3361ab8c908cc233dd529c0e76a5b
user=9b851e2e7cdaf8578b24227cb04a9f688861a6e5cec167fab9d5b1bc3dad4cc035ceb0a279883d3802298cfc8f8291d795cd98a2ce6b017461a08b83e692d402
試しにcookieの値をいろいろいじってみる
userを空にしてみた
FATAL ERROR: iv must be 16 bytes
signatureを空にしてみた
FATAL ERROR: Bad signature!
signatureを適当なsha256にしてみた
FATAL ERROR: Bad signature!
userの値をreverseしてみた
FATAL ERROR: bad decrypt
userの末端1バイトを削った
FATAL ERROR: wrong final block length
userは何かしらの暗号化されたデータ、signatureはそのhashみたいな認識で良いんだろうか。IVの情報から1block 16byteのブロック暗号ということはわかる。多分AESだろう。もしCBCモードを使っているならば、IVを適当に変更しても最初のブロック以外はうまく復号されるはず。と言うかnブロック目に適当なxorを入れることでn+1ブロック目の復号結果をいじれたんじゃないかな。
というわけで、二つのuser cookieからIV部分、暗号部分をそれぞれ切り出して混ぜたら FATAL ERROR: Bad signature! と言われた。復号は出来てるっぽいがsignatureがあわないと。
CBCモードでやってるっぽいのでPadding Oracle Attackで暗号文を復元できそう。
こんなかんじで出来た。今回のケースだと最終ブロックは全て0x10だったっぽいんだけど、なぜか最初の1バイトを求められなかったので飛ばした。
import requests from pwn import * from binascii import * import re URL='http://localhost:3000/' user=unhexlify(b'9b851e2e7cdaf8578b24227cb04a9f688861a6e5cec167fab9d5b1bc3dad4cc035ceb0a279883d3802298cfc8f8291d795cd98a2ce6b017461a08b83e692d402')[:-16] signature='ce4b1ccb5ee28ad81d38bc89c8526f46c2d3361ab8c908cc233dd529c0e76a5b' rack_session='BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiRWViMjM4MDA2YThjZjNiZDFmNGVk%0AODcyYTMyODgyYmEzZTI2NzM1ZTYxYzQ4NTFmOTBjZjliZDA3MjM2ZDlhODYG%0AOwBGSSILc2VjcmV0BjsARiINkBoXKVoJ9tRJIghrZXkGOwBGIiURwdBj0FCz%0AgSC4dqUax5Zijd8DNdmJCQm%2FGl9CulHjpg%3D%3D%0A--5af2792e0662bbeb14adce303225a06d275f96fb' m = '' last_byte = b'' l = 0 while len(user) > 16: for b in range(256): dummy_block = bytearray([0]*(15-l) + [b]) for b2 in last_byte: dummy_block += bytearray([b2 ^ (l+1) ^ user[-32:][len(dummy_block)]]) dummy_user = user[:-32] + dummy_block + user[-16:] r = requests.get(URL, cookies={'user': hexlify(dummy_user).decode(), 'signature': signature, 'rack.session': rack_session}) if 'Bad signature' in r.text: last_byte = bytearray([b ^ user[-32:][15-l] ^ (l+1)]) + last_byte l += 1 print(l, last_byte) break if l == 16: m = last_byte.decode() + m last_byte = b'' l = 0 user = user[:-16] print(m)
そして復号の結果、↓が得られた
UID 48
NAME baseuser
SKILLS n/a
ところで暗号文にUID 0を追加する方法だけど、C_Nを適当に改ざんしてC_N+1をUID 0に書き換えれば良さそう。IVの書き換えで大丈夫だろうか。 C1 = E(M1 + IV)として D(C1) + IV2 = M1 + IV + IV2からIV2を計算できる、はず。
と思ったけどsaltつきのハッシュ関数があるので改竄は出来なくて、Length Extension Attackをするしかない。暗号文を少し伸ばしてCn+1, Cn+2の二つのブロックを追加する。Cn+2がpayloadで、まあこれはC1と同じで、Cn+1はMn+2をうまくxorする為に使う。D(Cn+2) = M1 + IV なので Cn+2 = IV + M1 + pad("\nUID 0\n") とかにすると D(Cn+1) + Cn+1 = pad("UID 0")になって良さそう。
すると復号された文は次のようになるはず
UID 48
NAME baseuser
SKILLS n\a
\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10
なんかCn+1を復号したゴミ
UID 0
えー、ゴミの部分がわからないのでLength Extension Attackできなくないですか、ということで調べたらPadding Oracle Encryption Attackができそう。手順は Padding Oracle Attackのところに書いた
要するに
UID 48
NAME baseuser
SKILLS n\a
UID 0
と復号される暗号文をPadding Oracle Encryption Attackで作って、この平文に合うようにLength Extension Attackをやる。ソルバをがーっと書き直した。
何故かBad decryptoと怒られるので泣いてる。たぶんEncryption Attackがなにか良くないんだけど、何が良くないのかはわからない。他のところではうごいてるんだよな。
import requests from ptrlib import * from binascii import * import re URL = "http://localhost:3000/" r = requests.get(URL, allow_redirects=False) user = unhexlify(r.cookies["user"].encode()) signature = r.cookies["signature"] rack_session = r.cookies["rack.session"] print(signature) def oracle(user): r = requests.get( URL, cookies={ "user": hexlify(user).decode(), "signature": signature, "rack.session": rack_session, }, ) if "bad decrypt" in r.text: return False if "Bad signature" in r.text: return True raise Exception(r.text) def pad(bs, l): l2 = len(bs) % l return bs + bytes([l2]) * (l - l2) def unpad(bs): l = bs[-1] return bs[:-l] m = padding_oracle(oracle, user[16:], 16, unknown=b"A", iv=user[:16]) m = unpad(m) print(m) new_hash, data = lenext(SHA256, 8, signature, m, b"\nUID 0") new_user = padding_oracle_encrypt(oracle, plain=pad(data, 16), bs=16, unknown=b"A") print(repr(data)) print(new_user) print(repr(new_hash)) r = requests.get( URL, cookies={ "user": hexlify(new_user).decode(), "signature": signature, "rack.session": rack_session, }, ) print(r.text)