DefCamp CTF Qualification 2018 write-up
Harekazeで参加。決勝圏内の15位。
結構解けて満足している。
Junior
EagleEye
例) stegsolve
flag: DCTF{912c07726142de12943b76a89d40847028330f0a1a0be1ac24503c57242404ab}
RobotsVSHumans
robots.txt
に以下の記述。
Did you know that robots.txt is not the only .txt file in a website? BTW: I am against humans!
それなら、と humans.txt
にアクセスしてみたら、あった。
flag: DCTF{1091d2144edbffaf5dd265cb7c93e799c4659eb16ee79735b3bd6e09dd6e791f}
Passport
渡された demo.bin
をアップロードすると、
This is the demo Passport! You have to find its evil twin!
適当なファイルをアップロードすると、
Your Passport is invalid. Our MD5SUM detected a false document!
MD5を衝突させればよさそう。
$ md5sum demo.bin cee9a457e790cf20d4bdaa6d69f01e41 demo.bin
cee9a457e790cf20d4bdaa6d69f01e41
でググるとこの通り。
flag: DCTF{04c8d0052e3ffd8d21934e392c272a0494f23433901941c93fab82b50be27c1a}
終盤でDcuaに同点まで追いつかれて急遽解いたのだが、
DcuaはJuniorでない問題を解き、我々を突き放していった。
Web
Get Admin
問題概要
ソースコード付き。
$_SESSION['userid'] === "1"
ならフラグが得られる。
ログインすると、次のようにしてユーザ情報がcookieに入る。
<?php // これがないとシンタックスハイライトが効かなかったので...... $user = ['id' => $userid, 'username' => $username, 'email' => $email]; $user['checksum'] = crc32(compress($user)); // compress() は後述 $plaintext = compress($user); $ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', AES_KEY /* hidden */, OPENSSL_RAW_DATA, AES_IV /* hidden */); $cookie = base64_encode($ciphertext) . sprintf('%06d', strlen($plaintext)); setcookie('user', $cookie, time() + 60 * 60 * 24 * 30);
compress()
は、配列を受け取り、最初の要素から順にkeyとvalueの組を連結した文字列を返す。
この時、keyとvalueの区切りは ¡
、keyとvalueの組の区切りは ÷
である。
例えば、 ['id' => 123, 'username' => 'piyo', 'email' => 'piyo@piyo.com']
の場合、 $plaintext
は
id¡123÷username¡piyo÷email¡piyo@piyo.com÷checksum¡3405045270
となる。
また、セッションがない状態でcookieがある場合は、cookieから次のようにユーザ情報が復元される。
<?php $ciphertext = base64_decode(substr($_COOKIE['user'], 0, -6)); $plaintext_padded = openssl_decrypt($ciphertext, 'AES-128-CBC', AES_KEY, OPENSSL_RAW_DATA, AES_IV); $plaintext = substr($plaintext_padded, 0, intval(substr($_COOKIE['user'], -6, 6))); $user = decompress($plaintext); // decompress() は後述 $_SESSION['userid'] = $user['id'];
decompress()
は、文字列を受け取り、まず各keyとvalueの組に関して先頭から順に代入処理を行い、配列Aを生成する。
また、Aからchecksumを unset()
した配列Bを用意する。
その後、Aに含まれるchecksumと、B を compress()
したものの crc32()
が一致するかを確認し、一致した場合Bを返す。
目標
decompress()
は各keyとvalueの組に関して先頭から順に代入処理を行うため、
同じkeyが複数回現れた場合は、最後のvalueが残る。
そのため、先ほど compress()
で示した例を改変した
id¡123÷username¡piyo÷email¡piyo@piyo.com÷id¡1÷checksum¡3925280164
という文字列(Sとする)を decompress()
した場合、
Aは ['id' => '1', 'username' => 'piyo', 'email' => 'piyo@piyo.com', 'checksum' => '3925280164']
、
Bは ['id' => '1', 'username' => 'piyo', 'email' => 'piyo@piyo.com']
となる。
唐突に現れた 3925280164
は、checksumのチェックが通るよう、
Bを compress()
したものの crc32()
をあらかじめ計算したものである。
よって、Sを decompress()
させられれば、
checksumのチェックに引っかかることなく無事 $_SESSION['userid'] === "1"
となり、フラグが得られる。
解法
まず、emailを piyo@piyo.com
から piyo@piyo.com÷id¡1÷checksum¡3925280164
に変更する。
すると、cookieに値を入れる処理における $plaintext
は、
Sの crc32()
である 1599925957
をSの末尾に付与した
id¡123÷username¡piyo÷email¡piyo@piyo.com÷id¡1÷checksum¡3925280164÷checksum¡1599925957
(S'とする)となる。
また、cookieに入る値は
S'をAESで暗号化してbase64エンコードしたもの
+ 000097
となる。
このcookieの値のままでセッションを破棄した場合、
S' (= S + ÷checksum¡1599925957
) が decompress()
されるため、
checksumが 1599925957
で上書きされ、チェックに通らない。
そのため、cookieの値の末尾(復号結果の先頭何バイトを decompress()
するか)を調整し、
S'をAESで暗号化してbase64エンコードしたもの
+ 000075
とする。
すると、晴れてSが decompress()
され、フラグが得られる。
flag: DCTF{4EF853DFC818AFEC39497CD1B91625F9E6E19D34D8E43E56722026F26A95F13E}
なお、emailの代わりにusernameを細工してもよい。
ちょっとログインしづらくなるが。
Reverse
Ransomware
ransomware.pyc
と youfool!.exe
が渡される。
ransomware.pyc
をデコンパイルするとこうなる。
import string from random import * import itertools def caesar_cipher(x, y): y = y * (len(x) / len(y) + 1) return ''.join((chr(ord(i) ^ ord(j)) for i, j in itertools.izip(x, y))) f = open('./FlagDCTF.pdf', 'r') buf = f.read() f.close() allchar = string.ascii_letters + string.punctuation + string.digits password = ''.join((choice(allchar) for _ in range(randint(60, 60)))) buf = caesar_cipher(buf, password) f = open('./youfool!.exe', 'w') buf = f.write(buf) f.close()
60バイトのpasswordが確定すれば勝ち。
よくあるOTPの使い回しの問題と同様、少しずつ確定部分を広げていけば解ける。
元のファイルはPDFらしいので、先頭の %PDF-1.
辺りからスタートするとよい。
flag: DCTF{d915b5e076215c3efb92e5844ac20d0620d19b15d427e207fae6a3b894f91333}
Exploit
Online linter
CVE-2018-11235で任意のコードが実行できる。
/var/www/html/index.php
の最初の行にフラグが書いてある。
flag: DCTF{4a49b863ba931ac65b077a504b973d9ddab4f343b00651a0b4ff9b8d7575f41f}
Misc
Message
単語(スペースで分割した各文字列)が全て qwerty
、 asdfgh
、 zxcvbn
のいずれか(Xとする)で終わっており、
また数字や記号の前後が必ずXになっていることから、Xが何らかの単位となっていることが予想できる。
そこで、Xを適当な文字に置換してやると、
前後をXに挟まれた部分文字列(のうち、数字や記号でないもの)が、
キーボード上で順になぞるとアルファベットの形になることに気付く。
最終的にこうなる。
Lorem ipsum is simply dummy text of the printing and typesetting industry. Lorem ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of letraset sheets containing lorem ipsum passages, and more recently with desktop publishing software like aldus pagemaker including versions of lorem ipsum. DCTF{b66ecaaa90ad05df5dab33d71a8f70934408f3a5847a4c5c38db75891b0f0e32}lorem ipsum is simply dummy text of the printing and typesetting industry. Lorem ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of letraset sheets containing lorem ipsum passages, and more recently with desktop publishing software like aldus pagemaker including versions of lorem ipsum.
flag: DCTF{b66ecaaa90ad05df5dab33d71a8f70934408f3a5847a4c5c38db75891b0f0e32}
Broken TV
各走査線がランダムにrotateしているが、手作業で十分復元できる。
フラグ付近だけ抜き出した画像は以下の通り。
flag: DCTF{1e20cabc8098b16cfeefb05af0a9032bb953871d6d627e7f88b81d1a3c5fa809}