はじめに

12/05に開催されたFlatt Security Speedrun CTF#2に参加してきました。

初回はCODEBLUEの際に開催されていて、全完できなかったのでリベンジしに来ました。結論から言うと連敗です。

scoreboard

Scoreboardはこんな感じでした。なんとか3位に入れたのは昨日早解きの練習したおかげだと思いたいです。

Writeup

Writeupでは問題サーバのIPアドレスをlocalhostにしています。

実際にローカルで検証したと明記しているとき以外は問題サーバを直接叩いています。時間がもったいないので。

X

目標タイム 5:00

このCTFでは、問題を開始してからの時間が記録されます。

が、問題名は最初から開示されているので、まずはそこから問題をGuessします。

WebジャンルでXといえば、自分の脳ではとりあえずX-Forwarded-Forとかかなと思い、Burpを起動します。

準備ができたら開始し、問題ファイルをダウンロードしながらページを開き、レスポンスを見る前にRepeaterに投げます。

X-Forwarded-For: 127.0.0.1

をつけてみるも、You are not coming from 127.0.0.1!と怒られます。

というわけでX-系の奴を一通りつけて試すと、

x header is banned!と返ってきます。

う〜んなるほどね。

わからんのでXを消してみます

Forwarded-For: 127.0.0.1

flag{not_only_x-forwarded-for}

行けました。

タイムは2:21.5です。

busybox1

目標タイム 5:00

busyboxはまぁなんとなくわかるのでとりあえずスタート。

開いてみると入力欄があり、どうやらWebshellっぽいです。

とりあえずlsしてみると、

bun.lockb
index.html
node_modules
package.json
server.ts
tsconfig.json

はぇ〜、Bunなのか

Flagがどこにあるのかわからないので問題ファイルを見ます。

Dockerfileを見ると

RUN touch /flag && chown root:ctf /flag && chmod 660 /flagって書いてあったので、

cat /flagします

Banned!

ダメでした。

server.tsを見ると

if (["cat", "sh"].some((banned) => command[0].includes(banned))) {
  return Response.json({ error: "Banned!" });
}

と書いてあるので、ダメっぽいです。

catがダメならtacで行きます。

tac /flag

flag{you_can_read_any_file_without_cat}

行けました。tacってbusyboxにあったんだ

タイムは3:06.6です。

busybox2

目標タイム 10:00

まぁ同じ方法は無理だろうとは思いつつ、とりあえず試してみる

tac /flag

tac: /flag: Permission denied

おっとそういう感じか。

というわけで配布ファイルをちゃんと見ます。

なんかgetflag.cとかいうファイルがあるので、よく見ると/getflagを実行すればいいっぽいです。

/getflag

Only commands in /bin are allowed!

まぁそうだよね

コマンドの引数で実行させるということで、パッと思いついたfindを試しますが、findのexecのフォーマットを毎回忘れます。最近はfd使ってるしxargsでやっちゃうし

find . -exec /getflag {} \;

flag{you_can_directly_run_busybox_sh_btw}

いっぱい出てきた。Flagがいっぱいあってうれしい。

タイムは2:58.0です。

semgrep

目標タイム 20:00

さて、Speedrunでは休憩のタイミングが大事です。

ここまで割りと順調に進んできたので、休憩を入れます。

会場の地図を見て喫煙所を発見したので一服します。

返ったらまずはsemgrepをインストールし、ドキュメントなどを開いておきます。

あとcolimaを立ち上げてなかったので立ち上げます。

スタート

入力欄とRunボタンがありますが、よくわからん

なんとなくsemgrepでFlagにマッチさせるのかなと思い、"flag"とか/flag/iを入力してRunしてみるも、ダメでした。

ここで割りと大幅にロス。

配布ファイルを見てみると、全然違いました。入力したコードに対してsemgrepを実行し、問題なければ実行してくれる感じです。

semgrepのルールは153行に渡るYamlで書かれており、文字列リテラルとかevalとかimport, require, File, Bunなどが一通り禁止されています。

Webっぽくなってきた。

とりあえず重いのでローカルで環境を立ち上げます。

Web初心者でJavaScriptもBunも何もわからないので、ドキュメントとChatGPTを駆使しました。

ChatGPTに聞いたら文字列がString.fromCharCodeで書けるらしいので、Pythonのchrと同じ感じかなと思い、String.fromCharCode(102)+String.fromCharCode(108)+String.fromCharCode(97)+String.fromCharCode(103)を入力してRunしてみると、

flag

が返ってきたので、まぁ文字列リテラルのバイパスはできていそうです。

が、結局BunとかFileとかが禁止されているので、どうやってFlagを読み込むかわからず唸っていました。大幅ロス。

途中でふと思いついて

ev\u0061lを投げてみたらなんか行けたので、勝ったと思い

Bun.file("/flag").text()をエンコードして

ev\u0061l(String.fromCharCode(66)+String.fromCharCode(117)+String.fromCharCode(110)+String.fromCharCode(46)+String.fromCharCode(102)+String.fromCharCode(105)+String.fromCharCode(108)+String.fromCharCode(101)+String.fromCharCode(40)+String.fromCharCode(34)+String.fromCharCode(47)+String.fromCharCode(102)+String.fromCharCode(108)+String.fromCharCode(97)+String.fromCharCode(103)+String.fromCharCode(34)+String.fromCharCode(41)+String.fromCharCode(46)+String.fromCharCode(116)+String.fromCharCode(101)+String.fromCharCode(120)+String.fromCharCode(116)+String.fromCharCode(40)+String.fromCharCode(41))

を投げます。

flag{**dummy**}
               
               
┌─────────────┐
│ Scan Status │
└─────────────┘
  Scanning 1 file tracked by git with 8 Code rules:
                                                                                
  Language   Rules   Files          Origin   Rules                              
 ──────────────────────────        ────────────────                             
  js             8       1          Custom       8                              
  ts             8       1                                                      
                                                                                
                
                
┌──────────────┐
│ Scan Summary │
└──────────────┘

Ran 8 rules on 1 file: 0 findings.

こんな感じで用意したダミーのフラグが取れました。

が、リモートに投げるとなんか503が出ます。

そのままタイムアップです。敗北。

まとめ

Web初心者すぎて知らなかったんですが、String.fromCharCodeとかいうのは引数を複数取れるらしいです。俺がPythonで作ったツールは無駄でした。

まぁ最初の3問で早解きできたのは良かったです。そういう意味ではリベンジになった。

Tシャツも貰えて楽しかったので、また機会があればリベンジします。

あとUpsolveもします。5問目が気になるし4問目も想定解法じゃないっぽいし。

おわりに

この記事はn01e0 Advent Calendar 2023の6日目の記事です。

明日はあるかわかりません

また、IPFactory OB Advent Calendar 2023🎄GMOペパボエンジニア Advent Calendar 2023の6日目の記事も兼ねています。