ブラウザ上で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 に作成した。

参考 :