SECCON Beginners CTF 2021(#ctf4b) writeup

# SECCON Beginners CTF 2021(#ctf4b) writeup

結果はこんな感じでした

```txt
チーム名: SatoHack365
順位: 144
チームで解けた問題数: 13
チームで解けたスコア: 1110
自分が解けた問題数(メンバーが先に解いたものを含む): 8
自分が解けた問題のスコア: 575
```

去年は出てませんが，一昨年は 0 完だったので結構成長していました．

## crypto

### simple_RSA (75 pt)

受け取ったファイルを見ると公開鍵`e`が 3 と，ものすごく小さいです(一般的には 65537 など)．Low Public Exponent Attack ができます．

```python
import sys
import gmpy2

def long_to_bytes(x):
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

def low_public_exponent_attack(c, e, n=0):
    while True:
        m, exact_root = gmpy2.iroot(c, e)
        if exact_root == True:
            break
        c += n
    return int(m)

if __name__ == '__main__':
    n = ファイルに書かれた n
    e = 3
    c = ファイルに書かれた c
    m = low_public_exponent_attack(c, e, n)
    flag = long_to_bytes(m).decode().strip()
    print(flag)
```

```sh
ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}
```

## web

### osoba (51 pt)

app.py のコードをみます．

```python
page = request.args.get('page', 'public/index.html')
response = make_response(send_file(page))
```

ここでは URL クエリパラメータ`page`に与えられた値のパスにあるファイルを，そのまま出力しているみたいです．デフォルトでは`public/index.html`つまり index.html をそのまま出力します．よってディレクトリトラバーサル攻撃ができます．  
現在のルートディレクトリは`src`のようなので，`https://osoba.quals.beginners.seccon.jp/?page=../flag`にアクセスするとフラグが入手できます．

```sh
ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}
```

### Werewolf (70 pt)

`player.role` を `WEREWOLF`に変えることができればフラグが取れるらしいです．ここで，二つ確認すべき点があります．

**1)** Python には完全なプライベート変数は存在しません．
   以下のコードを見てみます．

```python
class Player:
    def __init__(self):
        self.__role = "VILLAGER"
    @property
    def role(self):
        return self.__role
```

このコードで`player = Player()`とした後に`player.role`の値は`_Player__role`とするとプライベート変数なのに取得することができます．~~カスだね，Python ちゃん~~．

**2)** `player.__dict__[k] = v`でキー`k`値`v`の変数を作成する．もし存在していれば上書き

よって以下のように POST を遅れば flag が取れます．

```sh
curl -w '\n' 'https://werewolf.quals.beginners.seccon.jp/' --data '_Player__role=WEREWOLF' -XPOST
```

```sh
ctf4b{there_are_so_many_hackers_among_us}
```

### cant_use_db (108 pt)

品物を買った後に走るスクリプトに
`time.sleep(random.uniform(-0.2, 0.2) + 1.0)`
という箇所があります．データベースを使用していないため，排他制御を気にしているのか 1 秒待っているみたいです．逆に言うと，お金があるかちゃんと確認してから 1 秒後にお金が取られます．そこで，このスリープ時間に急いでクリックして，もう一個リクエスト送ったら balance の値は不定になりそうですが，soup と noodles はちゃんと増えます．  
コツは Soup → Noodles の順で早く押すとだいたい Balance が $10000 になるので，あとはゆっくり Noodles を買って Flag をもらいに行きます．

```sh
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}
```

## misc

### git-leak (58 pt)

与えられたファイルに書いてある`reflog`コマンドを使ってみます．

```sh
e0b545f (HEAD -> master, origin/master) HEAD@{0}: commit (amend): feat: めもを追
80f3044 HEAD@{1}: commit (amend): feat: めもを追加
b3bfb5c HEAD@{2}: rebase -i (finish): returning to refs/heads/master
b3bfb5c HEAD@{3}: commit (amend): feat: めもを追加
7387982 HEAD@{4}: rebase -i: fast-forward
36a4809 HEAD@{5}: rebase -i (start): checkout HEAD~2
7387982 HEAD@{6}: reset: moving to HEAD
7387982 HEAD@{7}: commit: feat: めもを追加
36a4809 HEAD@{8}: commit: feat: commit-treeの説明を追加
9ac9b0c HEAD@{9}: commit: change: 順番を変更
8fc078d HEAD@{10}: commit: feat: git cat-fileの説明を追加
d3b47fe HEAD@{11}: commit: feat: fsckを追記する
f66de64 HEAD@{12}: commit: feat: reflogの説明追加
d5aeffe HEAD@{13}: commit: feat: resetの説明を追加
a4f7fe9 HEAD@{14}: commit: feat: git logの説明を追加
9fcb006 HEAD@{15}: commit: feat: git commitの説明追加
6d21e22 HEAD@{16}: commit: feat: git addの説明を追加
656db59 HEAD@{17}: commit: feat: add README.md
c27f346 HEAD@{18}: commit (initial): initial commit
```

僕もよくわかってませんが，`rebase`した後でも元に戻せるようです．`rebase`する前の`HEAD@{7}`に戻りましょう．

```sh
git reset --hard HEAD@{7}
```

すると flag.txt というファイルが出現(復元)されます．

```sh
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
```

### Mail_Address_Validator (76 pt)

ReDoS 攻撃という攻撃が成立します．運よく数日前に僕はスライドにまとめていました．

[正規表現の脆弱性 ReDoS 攻撃 を簡単に説明する回](https://www.slideshare.net/IshikawaRyusei/redos-248334868)

ちなみにこの問題の簡易版の問題も作り，公開しています(おそらく記事公開後，数日で消えます)．

[https://redos-server.herokuapp.com/](https://redos-server.herokuapp.com/)

詳しくはスライド参照していただきたいのですが，正規表現は下手に書くと match に時間がかかる場合があります．今回 main.rb には

```rb
pattern = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
```

と書かれていますが，後半の`+)*`が ReDoS できそうです．なので適当に`.`を入れて最後に`b`を入れてこんな感じにすればだいたい処理時間が爆発します．

```sh
aaa@a0a.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaab.aaa
```

```sh
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
```

### depixelization (136 pt)

フラグの文字列を画像に変換して，各文字の上に`P`,`I`,`X`の三文字を重ねて出力しているみたいです．そこで画像をいくつかに分割し，また ASCII 全ての文字を画像に変換して一致を比較とします．

「**画像をいくつかに分割**」
ソースコードより，画像は横 85px らしいです．

```python
import cv2

# 画像読み込み
img = cv2.imread("output.png")

height = img.shape[0]
width = img.shape[1]

for i in range(31):
    img1 = img[0:, i*85: i*85+85]
    cv2.imwrite("out/" + str(i) + ".png", img1)
```

「**ASCII 全ての文字を画像に変換**」

```python
import cv2
import numpy as np

flag = "@!#$%&'()*+,-./0123456789:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz{|}~"
flag = " "

print("FLAG: " + flag)
cnt= 0
for i in flag:
    images = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)
    # char2img
    img = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)
    cv2.putText(img, 'd', (0, 80), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)

    # pixelization
    cv2.putText(img, "P", (0, 90), cv2.FONT_HERSHEY_PLAIN, 7, (0, 0, 0), 5, cv2.LINE_AA)
    cv2.putText(img, "I", (0, 90), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)
    cv2.putText(img, "X", (0, 90), cv2.FONT_HERSHEY_PLAIN, 9, (0, 0, 0), 5, cv2.LINE_AA)
    simg = cv2.resize(img, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_NEAREST) # WTF :-o
    img = cv2.resize(simg, img.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)

    if images.all():
        images = img
    else:
        images = cv2.hconcat([images, img])
    cv2.imwrite("char/" + i + "_char.png", images)

```

これを実行した後，`md5sum`などで各画像をハッシュ化し，一致している画像ペアを探します．すると，以下のようにフラグが得られそうです．

```sh
ctf4b{1f_y0u_p1x_y0u_c4n_[ここ一文字謎]3p1x}
```

一文字だけ復元できませんでした．そこでその文字と PIX だけの文字を比較します．
![スクリーンショット 2021-05-23 0.24.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343837/1df00cb5-f449-1e0b-b747-e16c5c2cb24b.png)

上の画像が例の文字ですが，`o`のように見えます．ですが`o`と比較すると右上に少し何かがあったようです．そこで`d`と比較すると見た目は一致します．よって

```sh
ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}
```

です．後に知った情報ですが，OpenCV はバージョンによって結果が少し変わるそうです．なのでどうにか類似度を測ってやるのが最適そうです．