Atom や RDF を利用したXSS

Internet Explorer の悪名高い Content-Type: 無視という仕様を利用すると、AtomRDF/RSS を利用してXSSを発生できることがあります。条件的に対象となるWebアプリケーションは多くはないと思いますが、それでもいくつか該当するWebアプリケーションが実在することを確認しました。以下の例では Atom の場合について書いていますが RDF/RSS でも同様です。
例えば、http://example.com/search.cgi?output=atom&q=abcd という URL にアクセスすると、「abcd」という文字列の検索結果を Atom として返すCGIがあったとします。

GET /search.cgi?output=atom&q=abcd
Host: example.com

HTTP/1.1 200 OK
Content-Type: application/atom+xml

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" ...>
<title>Search Result: abcd</title> 
...

ここで、パラメータ q に UTF-7 で書かれたスクリプトを与え、PATH_INFO を無理やり付加して次のようにアクセスします。

GET /search.cgi/a.html?output=atom&q=%2BADw-%2Ftitle%2BAD4-%2BADw-[]script[]%2BAD4-alert(document.location)%2BADw-%2Fscript%2BAD4-
Host: example.com

HTTP/1.1 200 OK
Content-Type: application/atom+xml

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="[]http://www.w3.org/2005/Atom[]" ...>
<title>Search Result: +ADw-/title+AD4-+ADw-script+AD4-alert(document.location)+ADw-/script+AD4-</title>
...

Internet Explorer 6 でこのURL (http://example.com/search.cgi?output=atom&q=%2BADw-...)にアクセスすると、レスポンスの AtomUTF-7 で書かれた HTML と解釈されるため、スクリプトが動作してしまいます。なお、UTF-7 で書かれた箇所は以下の赤字のように解釈されます。

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="[]http://www.w3.org/2005/Atom[]" ...>
<title>Search Result: </title><[]script[]>alert(document.location)</[]script[]></title>
...

詳しい説明

もう少し説明します。該当するWebアプリケーションの条件は以下の通りとなります。「必須条件ではない」と書いてある項目は、攻撃が成功するために必ずしも必要となる条件ではありませんが、該当することで攻撃が成功する可能性が高くなる条件です。

攻撃者がAtom/RSS/RDF内に文字列を挿入可能
はてなダイアリーのように共有型のブログサイトで、攻撃者自身が書いたブログがAtom/RDF/RSSフィードとして出力される場合や、上の例のように検索結果をAtom/RDF/RSSとして出力するサイトなど、Atom/RDF/RSSに攻撃者自身が文字列を挿入できることが最初の条件になります。
レスポンスヘッダの Content-Type: が application/xml または text/xml でない
IE が Content-Type: を正しく認識できないため、HTMLと誤認することがこの手法によるXSSの最大の原因です。application/xml および text/xml は、IEでは正しく Content-Type を認識し XMLとして扱いますが、application/atom+xml または application/rss+xml または application/rdf+xml の場合には以下のPATH_INFOの項に示すようにURL中のファイル名からファイルタイプをHTMLと判断するためにXSSが発生します。
被害者が Internet Explorer 6 を利用している(必須条件ではない)
IE7では、application/atom+xml および application/rss+xml をサポートしているので、HTMLと誤認することはありませんが、application/rdf+xml は登録されていませんので、HTMLと誤認する可能性があります。IE6ではいずれも登録されていませんので、HTMLと誤認される可能性があります。これらの Content-Type が認識可能かどうかは、レジストリの HKCR\MIME\Database\Content Type 以下に該当するキーが存在するかどうかで確認できます。
レスポンスヘッダの Content-Type: で charset の指定がない(必須条件ではない)
Content-Type:にて charset を指定していない場合には、上記の例のように文字エンコーディングの自動選択機能によりUTF-7が採用されるため、スクリプトの挿入が可能となります。Atom/RSS/RDFにて CDATA セクションを使って <script> の直接的な記述が可能な場合には、charset の有無とは関係なくスクリプトの挿入が可能となりえます。
レスポンスに日本語(など)が含まれていない(必須条件ではない)
UTF-7スクリプトを埋め込むために、IE6の文字エンコーディングの自動判別機能を利用するので、レスポンスに日本語(などのUS-ASCII以外の文字列)が含まれている場合には、UTF-7と判断されずにスクリプトが動作しない可能性が高くなります。
PATH_INFOを付加してもレスポンスが返ってくる
IEは、認識できない Content-Type の場合には、URLパスからファイルタイプを決定します。そのため、URLのクエリの前に http://example.com/search.cgi/a.html?... といったふうに PATH_INFO を付加した場合には、レスポンスが(実質的には)XMLであってもHTMLとして扱われます。こういった形でのアクセスを許さないWebアプリケーションでは、この手法によるXSSはおそらく不可能となります。

これらの条件を満たすWebアプリケーションでは、XSSが発生することになります。
対策としては、以下のいずれかを当てはめればよいと思います(複数組み合わせてもよいと思います)。

Content-Type: として text/xml または application/xml を使用する
開発者として不本意かも知れませんが、Content-Type: を text/xml または application/xml とすることで、IEにてHTMLと誤認されることはなくなります。
Content-Disposition: によるファイルタイプの強制
Content-Type:が未登録の場合、Content-Disposition:ヘッダよりファイルタイプが決定されるようです。ですので、Content-Disposition: inline; filename="foo.xml" のようなレスポンスヘッダを含めておくことで、PATH_INFO に *.html と付与された場合でもHTMLと誤認されることはなくなります。
charset の明記とHTMLメタキャラクタのエスケープ
レスポンスヘッダで charset を明記し、XML中でも CDATA を使わずに < や > などをエスケープしてやることで、HTMLとして解釈された場合でもスクリプトの挿入を防止できます。

ちなみに

ちなみにこの手法は、Atom/RDF/RSS に限らず IE が認識できない Content-Type: 全般に当てはまります。例えば「Kazuho@Cybozu Labs: JSONP - データ提供者側のセキュリティについて」では、 Content-Type: として text/javascript というこれまた IE が認識できない Content-Type: を返しているために XSS が発生する可能性がある点について書かれています。
実際には、攻撃者が自由にスクリプト文字列を挿入でき、なおかつ IE が認識できない Content-Type: を返すWebアプリケーションというと、JSONPAtom/RSS/RDF 以外にはぱっと思いつきませんが、そういったものを作るときには充分注意しましょう。