Anti-XSS Library v3.1を試す

Anti-XSSライブラリのV3.1から、GetSafeHtml()やGetSafeHtmlFragment()といったスタティックメソッドが用意されました。これらのメソッドは、入力として与えられたHTMLやHTML断片から、JavaScriptを除去するためのものです。

(参考)HTML Sanitization in Anti-XSS Library – Security Tools

今回は、HTML断片を処理するGetSafeHtmlFragmentメソッドを試してみました。

挙動を見る限り、タグ/属性に加えてCSSホワイトリストベースで処理されます。かなり出来がよいライブラリです。IE8のtoStaticHTML関数がいまいちだったので(参考)、余り期待していませんでしたが、少なくともtoStaticHTMLよりもはるかによいです。

ただ現時点で実際のサイトに適用するうえでは問題もあります。ライブラリに関する情報量が少なく、どのようなタグや属性がホワイトリストに載っているのかが分かりません。また、許可するタグなどをカスタマイズする方法も分かりませんでした。ホワイトリストのカスタマイズは、類似のライブラリの多くに用意されている機能であり、それがないとしたら実用性の面で問題となるでしょう。

以下はメモです。網羅的なものではありませんが、参考まで載せておきます。

<要素の内容>
入力1:<b><&#x30;&#x3C;&#x3042;</b>
出力1:<b>&lt;0&lt;あ</b>

要素内容の文字参照はデコードされ、再びエンコードされる。

<属性値>
入力1:<img alt=111 alt='222' alt=3"'4&<&lt;>
出力1:<img alt="111" alt="222" alt="3&quot;'4&amp;&lt;&lt;">

属性値は無条件にダブルクォートで括られる。文字参照はデコードされ、再びエンコードされる。

<NULL文字>
入力1:<b>111[NULL]222&#0;333</b>
出力1:<b>111222&amp;#0;333</b>

要素内容のNULL文字は削除される。「&#0;」は「&amp;#0;」になる。

<不明な要素/属性>
入力1:<ab>111</ab>
出力1:111

入力2:<b ida="1">222</b>
出力2:<b>222</b>

ホワイトリストにない要素や属性は除去される。本当は、どのような要素や属性がホワイトリストに載っているかが重要だが、今回は検証に時間がかかるので調べていない。

<壊れた要素/属性>
入力1:<b id="3"
出力1:&lt;b id=&quot;3&quot;

入力2:<b id="3>
出力2:&lt;b id=&quot;3&gt; 

入力3:<p>111
出力3:<p>111 </p>

入力4:<b/title="1">222</b>
出力4:<b title="1">222</b>

入力5:<b title!#$%&="111">222</b>
出力5:<b>222</b>

入力6:<img """>111
出力6:<img>111

入力7:<p <s>111</s>
出力7:<p>111</p>

入力8:<style>
出力8:<style></style>
    <div></div>

HTMLはParseされ再構築される。なぜか、空のstyle要素の後にdivが付く。

<HTMLコメント>
入力1:111<!-- 222 -->333
出力1:111333

入力2:<!--[if gt IE 4]>111<![endif]-->222
出力2:222

HTMLコメントは除去される。

<特殊な構文>
入力1:<b><![CDATA[111]]>222</b>
出力1:<b>222</b>

入力2:<?import><?xml version="1.0"?>111
出力2:111

CDATAセクションなどは解釈されず、除去される。

URI属性>
入力1:<img src="http://example.com/">
出力1:<img src="http://example.com/">

入力2:<img dynsrc="httpxxx&#x3A;//example.com/">
出力2:<img dynsrc="">

入力3:<img dynsrc="&#x1;ho&#xD;ge://example.com/">
出力3:<img dynsrc="">

入力4:<img src="data:image/gif;base64,R0lGODdhAQABAIABAAAAAP///ywAAAAAAQABAAACAkQBADs=">
出力4:<img src="">

入力5:<img src="./hoge:xxx">
出力5:<img src="./hoge:xxx">

URI属性のスキームはホワイトリストで処理されている模様。

<壊れた文字参照
入力1:<b>&#1112322343423;&hoge;</b>
出力1:<b>&amp;#1112322343423;&amp;hoge;</b>

入力2:<b>&#xD800;</b>
出力2:<b>[0xEFBFBD]</b>  (=U+FFFD。UTF-8出力の場合)

入力3:<b>&#x000030;&#x000000030;</b>
出力3:<b>0&amp;#x000000030;</b>

デコードできない文字参照は、「&」がエンコードされるかU+FFFD(Replacement Character)になる。

<ID属性>
入力1:<b id="aaa">111</b>
出力1:<b id="x_aaa">111</b>

入力2:<style>.aaa {color: red;}</style>
出力2:<style>
    <!--
    .x_aaa
    	{color:red}
    -->
    </style>
    <div></div>

ID属性は頭に「x_」を付けられる模様。CSSセレクタのIDも同じ。

CSS 不明なプロパティ/値>
入力1:<b style="hoge: 1px;">111</b>
出力1:<b style="">111</b>

入力2:<b style="width: aaa; color: red;">111</b>
出力2:<b style="width:aaa; color:red">111</b>

入力3:<b style="width: aaa(); color: red;">111</b>
出力3:<b style="color:red">111</b>

入力4:<b style="color: hoge;">111</b>
出力4:<b style="color:hoge">111</b>

入力5:<b style="color: $red">111</b>
出力5:<b style="">111</b>

プロパティにはホワイトリストが適用されるようで、不明なプロパティは削除される。ただし、値のチェックはそれほど厳密ではなく、「color:hoge」や「width:aaa」は通る。しかし、「color:$red」や「width: aaa()」のようなものは削除される。

CSS バックスラッシュエンコード
入力1:<b style="colo\0072: re\0064;">111</b>
出力1:<b style="color:re\0064">111</b>

CSSプロパティの「\」エンコードはデコードされる。

CSS コメント>
入力1:<b style="color/* yyy */: red;">111</b>
出力1:<b style="color: red">111</b>

入力2:<b style="color: re/*xxx*/d;">111</b>
出力2:<b style="color:red">111</b>

入力3:<b style="font-family: '/* xxx */ hoge';">111</b>
出力3:<b style="font-family:'/* xxx */ hoge'">111</b>

CSSコメントは除去される。ただし、「'」で括られた文字列リテラルは理解しているようで、文字列リテラル内のコメントは除去されない。

CSS 文字列リテラル内のバックスラッシュ>
入力1:<b style="font-family: '\\\';">111</b>
出力1:<b style="">111</b>

入力2:<b style="font-family: '\\\\';">111</b>
出力2:<b style="font-family:'\\\\'">111</b>

入力3:<b style="font-family: '&#xA5;';">111</b>
出力3:<b style="font-family:'?'">111</b>   (=Shift_JIS出力の場合)

かつてのhtmlpuriferは、CSSの「'」で括られた文字列リテラル内で「\」や「U+00A5」を使うことでXSSできたので、同じことを試してみる(http://htmlpurifier.org/svnroot/htmlpurifier/tags/3.1.1/NEWS)。

入力1,2をみると、文字列リテラル内の「\」によるエスケープを解釈していることが分かる。入力3では「U+00A5」を使って「'」からの脱出を試みているが、ASP.NETで「responseEncoding="shift-jis"」にした場合、「U+00A5」は「?」に変換されるので、攻撃に失敗する模様(設定によっては攻撃に成功するかもしれないが、よく分からない)。

CSS 文字列リテラルセレクタ内の特殊記号>
入力1:<b style="font-family: 'expression(alert(1))';">111</b>
出力1:<b style="font-family:'expression(alert(1))'">111</b>

入力2:<b style="font-family: '</あ&';">111</b>
出力2:<b style="font-family:'\3C /あ&amp;'">111</b>

入力3:<style>.aaa\30 {font-family: '</あ&-->';}</style>
出力3:<style>
    <!--
    .x_aaa\30 
    	{font-family:'\3C /あ&--\3E '}
    -->
    </style>
    <div></div>

入力4:<style>.aaa>.bbb {font-family: monospace;}</style>
出力4:<style>
    <!--
    .x_aaa > .x_bbb
    	{font-family:monospace}
    -->
    </style>
    <div></div>

文字列リテラル内では「(」「)」が使える。文字列リテラル内の「<」「>」はバックスラッシュによりエンコードされる。セレクタの「>」はエンコードされない。

CSS その他>
入力1:<style>@import 'http://example.com/';</style>
出力1:<style>
    <!--
    -->
    </style>
    <div></div>

入力2:<style>@\69 mp\ort '//example.com/';</style>
出力2:(出力1と同じ)

入力3:<p style="background-image: url('http://example.com/');">111</p>
出力3:<p style="">111</p>

入力4:<p style="background-image: url\28'//example.com/'&#x5C;29;">111</p>
出力4:<p style="">111</p>

@import規則は使えない。url()も使えないようになっている。