[PR]小規模ECサイトに最適なWAF、SiteGuard Lite

徳丸浩の日記


2009年03月11日 文字コードのセキュリティ問題はどう対策すべきか

_U+00A5を用いたXSSの可能性

前回の日記では、昨年のBlack Hat Japanにおける長谷川陽介氏の講演に「趣味と実益の文字コード攻撃(講演資料)」に刺激される形で、Unicodeの円記号U+00A5によるSQLインジェクションの可能性について指摘した。

はせがわ氏の元資料ではパストラバーサルの可能性を指摘しておられるので、残る脆弱性パターンとしてクロスサイト・スクリプティング(XSS)の可能性があるかどうかがずっと気になっていた。独自の調査により、XSS攻撃の起点となる「<」や「"」、「'」などについて「多対一の変換」がされる文字を探してきたが、現実的なWebアプリケーションで出現しそうな組み合わせは見つけられていない。

一方、U+00A5が処理系によっては0x5C「\」に変換されることに起因してXSSが発生する可能性はある。JavaScriptがからむ場合がそれだ。しかし、実際にXSS脆弱性が発生するには、次のような状況を想定する必要がある。

  • 入力(HTTPリクエスト)はUnicodeで受け取る
  • 内部の処理もUnicodeで行われる
  • 出力(HTTPレスポンス)のエンコーディングはShift_JISあるいはEUC-JP

すなわち、入力(リクエスト)と出力(レスポンス)で異なる文字エンコーディングを想定しなければならない。これ自体は現実性が薄い。

この問題については、既に佐名木氏らの研究がある。佐名木氏は、Apache Tomcatに着目して以下のように記述している。

実験のポイントは、Tomcat の仕様変更である。Tomcat4 系からTomcat5 系へのバージョンアップによって、クエリー文字列は常にUTF-8 として受け取るように仕様が変更された。

この仕様変更に着目することで、ANSI の世界でデータ処理、そしてデータ出力を行っているJavaServlet に対してUTF-8 の世界の文字を与えることができる。

[Unicodeとサニタイジング回避テクニック ver1.6より引用]

Tomcatのクエリ文字列の文字化け問題はFAQであって、現実には「常にUTF-8として」受け取られるわけではなく、server.xmlの設定により、useBodyEncodingForURI="true" (クエリ文字列の文字エンコーディングをPOSTのエンコーディングと一致させる)を指定することができる。従って、佐名木氏の指摘しておられる状況もなくはないだろうが、もう少し現実的に *ありそうな* 可能性を検討したい。

そこで私は、HTTPリクエストの文字エンコーディングを「自動認識」させている場合に注目して調査を行った。各処理系に対する考察を以下に述べる。

Javaの場合

Javaは前述のように、U+00A5が\x5Cに変換されるので有力な候補だが、J2SEの文字エンコーディング自動判定機能は、JIS系の文字エンコーディングの範囲で行われる(JISAutoDetect)ため、上記の条件を満たすことができない。文字エンコーディングの自動判定を自作することは可能だが、検討の対象からは外すことにした。

PHPの場合

PHPは文字エンコーディングの自動判定が柔軟だが、一方、U+00A5が全角の円記号「¥」に変換されるため、XSSには利用できない。

Perlの場合

PerlにはJcode.pmやEncode.pmに文字エンコーディングの自動判定機能がある。しかし、UnicodeからShift_JISなどへの変換に際してU+00A5が「?」に変換されるため、やはりXSSには利用できない…と思っていた。最近までは。

しかし、私がITproに連載している連載中の記事「第6回■異なる文字集合への変換がぜい弱性につながる 」に対して、id:nihenさんからブックマークコメントを頂戴した。

【Perl(Encode.pm).(略).では発生しない】cp932では発生するです。http://cpansearch.perl.org/src/DANKOGAI/Encode-2.31/ucm/cp932.ucm

すなわち、UTF-8からShift_JISへの変換だとU+00A5は「?」に変換されるが、cp932(Windowsの機種依存文字を考慮したShift_JIS)への変換の場合は「\」(\x5C)に変換されるというのだ。確認したところ、たしかにそうなる。これで、U+00A5によるXSSの可能性が出てきた。さっそく試してみよう。

以下にサンプルコードを示す。

#!/usr/bin/perl
use CGI;
use utf8;
use Encode;
use Encode::Guess qw/utf8 shiftjis euc-jp/;

my $query = CGI->new;
my $p = decode 'Guess', $query->param('p');
# 制御文字のチェック…省略
# 次の行はJavaScript文字列リテラルのエスケープ
$p =~ s/(?=[\\\'\"])/\\/g;     # \ → \\  ' → \' " → \"
$p = $query->escapeHTML($p);   # HTMLエスケープ

print encode 'cp932', <<EOT;
Content-Type: text/html; charset=Shift_JIS

<html>
<body onload="alert('$p');">
テスト
</body></html>
EOT

この簡単なスクリプトは、クエリストリングpの値をalertダイアログに表示するだけの簡単なものだ。処理の流れは以下のようになる。

  • 文字エンコーディングの自動判定候補として、UTF-8、Shift_JIS、EUC-JPを指定
  • クエリストリングpを読み込み、文字エンコーディング自動判定でUTF-8に変換
  • JavaScript文字列リテラルとしてのエスケープ
  • HTMLエスケープ
  • 文字エンコーディングcp932を指定してHTML生成
  • body要素のonloadイベントハンドラにalert関数を生成

JavaScriptの動的生成に対して必要なエスケープ処理については、過去にXSS対策:JavaScriptのエスケープ(その2)などで説明した通りである。

私はそもそもJavaScriptの動的生成を推奨していないが、イベントハンドラにJavaScriptを置く場合は比較的シンプルに考えられる。上記のように、JavaScriptとしてのエスケープとHTMLのエスケープを2段階で行えばよい。面倒ではあるが、SCRIPT要素に置く場合のようにデータの途中に</SCRIPT>が出てくるような特殊ケースは考えなくて良い。

このスクリプトに対して、以下のような入力を与える(U+00A5は赤色全角の円記号で記述)。

');alert(document.cookie);//

この文字列は以下のように処理される。まずJavaScriptのエスケープ処理(' → \')。

\');alert(document.cookie);//

次にHTMLエスケープ(' → &#39;)

\&#39;);alert(document.cookie);//

そしてcp932に変換( → \)

a\\&#39;);alert(document.cookie);//
この文字列はJavaScriptの実行に際して、HTMLデコードされ以下のようになる。
alert('a\'');alert(document.cookie);//');"

すなわち、JavaScriptの文字列リテラルが突破され、第二のalertが追加された。XSSの成功である。

対策

この問題の根本原因は、UTF-8→cp932(Shift_JIS)の変換に伴う多対一変換にある。従って、文字エンコーディングを全てUTF-8に統一することで根本対策となる。しかし、一般的には、携帯電話や電子メールなど、JIS系文字集合を使わざるを得ない場合もあり、ブラウザとのやりとりはShift_JIS(あるいはEUC-JP)、プログラム内部ではUTF-16やUTF-8というケースは多いだろう。

このような場合は、プログラムの実行環境はUnicodeだが、処理対象となる文字集合をJIS系文字集合(マイクロソフト標準キャラクタセットなど)に限定することで対策が可能だ。具体的には、入力時の文字エンコーディング自動判定をやめ、エンコーディングを明示することだ。だが、もっとよい方法があるかもしれない。

一つの可能性として、長谷川氏の講演資料に指摘されているような「検査後には変換しない」すなわち、変換してから検査(エスケープ)する方法もある。しかし、PerlはShift_JISの文字列処理には対応していないので、cp932(Shift_JIS)に変換してからのエスケープも容易ではない。

また、「もっとよい方法」とは、従来言われてきた「過剰エスケープ」を指すわけではない。上記の例で言えば、スラッシュ「/」を「\/」にエスケープすれば、JavaScriptのコメント「//」が有効でなくなり、JavaScriptの実行エラーになるので攻撃は成立しない。しかし、そのような対策はアドホックで、理論的な裏付けに乏しいものだ。

また、今回は取り扱わないが、文字エンコーディングを利用した攻撃についても同様のことが言える。PerlのEncode::decodeは文字エンコーディングのチェックを行うので、そもそも不正な文字エンコーディングについては除去してくれる。エラーにしたければ(そうするべきだが)、オプションの第3パラメータCHECKにEncode::FB_CROAKを指定すればよい。それでも残る問題は処理系のバグ(脆弱性)なのだ。処理系のバグに対してアプリケーション側で対策しなければならない場合もあるだろうが、それは原因の所在を明確にした上での話だ。

文字コードのセキュリティ問題に関しては、公開されている情報が非常に少ない。とくに文字集合をアプリケーション開発の際にどのように取り組むべきかという議論はほとんどなされていないように思う。私は、ITproの連載の中で私見を述べているが、先行する研究がほとんどないので原理から手探りで検討している状態だ。この問題に対する活発な議論が行われることを期待する。



[PR]小規模ECサイトに最適なWAF、SiteGuard Lite

ockeghem(徳丸浩)の日記はこちら
HASHコンサルティング株式会社

最近の記事

最近のツッコミ

  1. 徳丸 浩 (03-29)
  2. とおりすがり (03-28)
Google