本当は怖い文字コードの話

第6回先行バイトの埋め込み

今回は、⁠先行バイトの埋め込み」という攻撃方法について紹介します。

ご存じのとおり、ほとんどの符号化方式(文字エンコーディング)においては、ひらがなや漢字などASCII以外のほとんどの文字は、1文字が複数バイトにて構成されています。たとえば、ひらがなの「あ」は、Shift_JISにおいては0x82 0xA0という2バイト、UTF-8においては0xE3 0x81 0x82という3バイトで表現されます。

攻撃者がマルチバイト文字の先行バイト部分だけを与えることにより、本来存在している後続の文字を無効にしてしまうのが、今回紹介する「先行バイトの埋め込み」という攻撃方法です。

先行バイト埋め込みの具体例

では、具体的な例を見ていきましょう。

たとえば、Shift_JISで書かれたHTMLとして、次のようなものがあったとします。

name: <input type=text value="" />
mail: <input type=text value="" />

ここで、攻撃者がnameとして最初の<input>に0x82というバイト値を、2番目の<input>にmailとして「onmouseover=alert(1)//」という文字列を与え、Webアプリケーションが以下のようなHTMLを生成したとします。

name: <input type=text value="[0x82]" />
mail: <input type=text value="onmouseover=alert(1)//" />

最初の<input>のvalueとして0x82というバイト値が与えられています。0x82はShift_JISにおいては先行バイトとして使われるバイト値ですので、多くのブラウザは0x82と続くダブルクォート(")を合わせて1文字だと解釈してしまいます。

このため、ブラウザの解釈するHTMLとしては

name: <input type=text value="[0x82][0x22]>
mail: <input type=text value="onmouseover=alert(1)//" />

のようになり、下線部すべてがvalue属性の値として解釈され、またonmouseoverイベントが有効になるため、結果としてXSSが成立してしまいます。

なお、この例はIE7、IE8、Firefox 3、Opera 10.00 Betaでスクリプトが動作することを確認しましたが、Safari 4.0、Google Chrome 2.0ではスクリプトは動作しませんでした。

もうひとつの例として、IE8に搭載されたXSSフィルタのケースを紹介します。

IE8では、ブラウザ側でXSSを防ぐ試みの一環として、XSSフィルタが搭載されています。XSSフィルタは、リクエストとレスポンスの相互をチェックし、リクエスト中に指定された<script>などがレスポンスのHTML中に有効な状態で含まれていれば、XSSであると判断してレスポンスを書き換え、挿入されたスクリプトが動作しないようにします。

たとえば、典型的なXSSの例として以下のようなHTTPでのやり取りがあったとします。

GET /search.cgi?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1
Host: example.jp

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<html>
....
<script>alert(1)</script>
....

リクエストに指定した「%3Cscript%3Ealert(1)%3C%2Fscript%3E」をデコードした「<script>alert(1)</script>」がレスポンスに含まれているのが見て取れます。

IE8のXSSフィルタはこのリクエストとレスポンスの双方から、XSSが発生していると判断し、レスポンスの該当部分を「<sc#ipt>alert(1)</script>」のように書き換え、スクリプトが動作しないようにします。

IE8 beta 2の時点では、このXSSフィルタによる防御を、以下のようなリクエストを発行することで回避可能でした。

対象がUTF-8の場合
http://example.com/?%3cscript%20%E2%3Ealert(1);...
http://example.com/?%E2%22onmouseover=alert(1)
対象がShift_JISの場合
http://example.com/?%3cscript%20%81%3E%3ealert(1);...
対象がEUC-JPの場合
http://example.com/?%3cscript%20%E0%3Ealert(1);...
http://example.com/?%E0%22onmouseover=alert(1)

いずれの場合でも、それぞれの符号化方式でのマルチバイト文字の先行バイト部分を挿入することで、IE8のXSSフィルタによる「>」「"」の検出を回避することができました(現在のIE8ではこの問題はすでに修正されています⁠⁠。

対策

現実的にこの攻撃方法が通用する場面はそう多くはありません。たとえば、最初の例では、input要素の各属性の値をダブルクォート(")で囲んでおけば、HTMLが崩れることはありますが、XSSにつながる可能性はほとんどありません。

name: <input type="text" value="[0x82]" />
mail: <input type="text" value="onmouseover=alert(1)//" />

このように、value属性の値として下線部の部分が該当してしまいHTMLの構造は崩れてしまいますが、イベントハンドラは無効なままですのでXSSにはつながりません。XSSへの対策を目的とするわけではありませんが、HTMLの各属性はダブルクォートで囲むことを徹底しておくほうがいいでしょう。

この攻撃方法への根源的な対策としては、入力された文字列の各文字がアプリケーション側が想定している文字コードとして、正しいか1文字ずつ検証するという方法が挙げられます。実際に1バイトずつ確認するコードを書くのは非常にコストがかかると思われますので、いったん他の符号化方式に変換することによって、不正なバイト列を排除するという方法が現実的ではないかと思います。

一例として、PerlのEncodeを使ってマルチバイト文字の先行バイトによるXSSを防ぐよい例を小飼弾氏が書かれていますので紹介しておきます。

今回説明した攻撃方法は、マルチバイト文字の特性を積極的に悪用した、まさに文字コードを利用した攻撃の真髄と言えると思います。また、今回は具体例としてXSSを挙げましたが、この攻撃方法自体はXSSに限らず, SQLインジェクションやOSコマンドインジェクションをはじめ、文字列を扱う箇所であればあらゆる場面で攻撃につながる可能性があると思いますので、注意するようにしましょう。

お知らせ

今年も、IPA主催のセキュリティ&プログラミングキャンプが開催されます。私も講師としてセキュリティコース、プログラミングコース両方の末席を汚させていただきます。22歳以下の学生を対象に参加者を募集していますので、みなさんの応募をお待ちしています。

セキュリティ&プログラミングキャンプ2009
URL:http://www.jipdec.or.jp/camp/

記事・ニュース一覧

→記事一覧