2009年03月03日 19:00 [Edit]

perl - EncodeでXSSを防ぐ

camel

良記事。

だけど、問題点のみ具体例があって、対策にないのが片手落ちに感じられたので、その点を補足。


結論だけ言ってしまえば、Perlなら以下の原則を守るだけです。

404 Blog Not Found:perl - Encode 入門
すでにOSCONでもYAPCでも、あちこちそちこちでこの基本方針に関しては話したのですが、ここ 404 Blog Not Found でも改めて。 Perl で utf8 化けしたときにどうしたらいいか - TokuLog 改め だまってコードを書けよハゲ
入り口で decode して、内部ではすべて flagged utf8 で扱い、出口で encode する。これがすべてです!とにかくこの基本方針をまもっていれば幸せになれます。

不正な文字エンコーディング(1)-- 冗長なUTF-8符号化問題

これは、以下のスクリプトを動かしてみればわかります。

#!/usr/local/bin/perl
use strict;
use warnings;
use Encode;
for my $bytes ( "\x2F", "\xC0\xAF", "\xE0\x80\xAF", "\xF0\x80\x80\xAF" ) {
    my $utf8 = decode( 'utf8', $bytes );
    print encode( 'ascii', $utf8, Encode::FB_PERLQQ ), "\n";
}
/
utf8 "\xC0" does not map to Unicode at ...
utf8 "\xE0" does not map to Unicode at ...
utf8 "\xF0" does not map to Unicode at ...

あえてわかりやすくするために、出力はascii、asciiにない文字はperl qqにするようにしてみました。

/
\x{fffd}\x{fffd}
\x{fffd}\x{fffd}\x{fffd}
\x{fffd}\x{fffd}\x{fffd}\x{fffd}

\x{fffd}、すなわちU+FFFDというのは REPLACEMENT CHARACTER のこと。マップが存在しない場合、エラー指定をしない場合はEncodeはこれを割り当てます。上記の例では「一種類の文字の冗長な表現」ではなく、「四種類の全く別の文字列」として解釈しているのでOKというわけです。

さらに以下のようにエラー指定をすることで、不正(invalid)なUTF-8を受け付けないようにも出来ます。

#!/usr/local/bin/perl
use strict;
use warnings;
use Encode qw(encode decode :fallback_all); # for fallback constaqnts
for my $bytes ( "\x2F", "\xC0\xAF", "\xE0\x80\xAF", "\xF0\x80\x80\xAF" ) {
    eval {
        my $utf8 = decode( 'utf8', $bytes, DIE_ON_ERR | LEAVE_SRC );
        print encode( 'ascii', $utf8, FB_PERLQQ ), "\n";
    };
    warn $@ if $@;
}

不正な文字エンコーディング(2)-- 半端な先行バイトによるXSS

まず、脆弱性がある場合。

#!/usr/local/bin/perl
use strict;
use warnings;
use CGI;
my $q      = CGI->new;
my $e_name = $q->escapeHTML( $q->param('name') );
my $e_addr = $q->escapeHTML( $q->param('addr') );
print <<EOT;
Content-Type: text/html; charset=Shift_JIS

<html><head><title>Vulnerable</title></head>
<body><form action="$ENV{SCRIPT_NAME}">
Name:<input name=name value="$e_name"><br>
Addr:<input name=addr value="$e_addr"><br>
<input type="submit">
</form></body></html>
EOT
sjis-ng.cgi?

次に、Encodeを使って脆弱性に対処した場合。

#!/usr/local/bin/perl
use strict;
use warnings;
use CGI;
use Encode;
my $q      = CGI->new;
my $e_name = $q->escapeHTML( decode 'sjis', $q->param('name') );
my $e_addr = $q->escapeHTML( decode 'sjis', $q->param('addr') );
print encode 'sjis', <<EOT;
Content-Type: text/html; charset=Shift_JIS

<html><head><title>Not Vulnerable</title></head>
<body><form action="$ENV{SCRIPT_NAME}">
Name:<input name=name value="$e_name"><br>
Addr:<input name=addr value="$e_addr"><br>
<input type="submit">
</form></body></html>
EOT
sjis-ok.cgi?

不完全な文字エンコーディング処理(3)-- UTF-7によるXSS

まず、脆弱性がある場合。ここではあえて明示的にtext/html; charset=UTF-7を指定して、IE以外のブラウザーでも確認できるようにしました。

#!/usr/local/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
my $p = $q->escapeHTML( $q->param('p') );
print <<EOT;
Content-Type: text/html; charset=UTF-7

<html><head><title>Vulnerable</title></head>
<body>
$p
</body></html>
EOT
utf7-ng.cgi?p=

次に、Encodeを使って脆弱性に対処した場合。記事の指摘のとおりtext/html; charset=UTF-7text/html; charset=UTF-8などとするだけで直りはするのだけど、Encodeを使えばtext/html; charset=UTF-7というまずありえない設定でも脆弱性を防ぐことが出来ます。

#!/usr/local/bin/perl
use strict;
use warnings;
use CGI;
use Encode;
my $q = CGI->new;
my $p = $q->escapeHTML( decode 'UTF-7', $q->param('p') );
print encode 'UTF-7', <<EOT;
Content-Type: text/html; charset=UTF-7

<html><head><title>Not Vulnerable</title></head>
<body>
$p
</body></html>
EOT
utf7-ok.cgi?p=

まとめ

繰り返しになるけれども、こういう記事の場合、問題点の指摘が具体的な場合には対策も同じぐらい具体的でないと、脆弱性に対する恐怖心が上がるだけで終わってしまうような気がします。「別記事でkwsk」という手もあるけれども、「こうすればより便利」ではなく、「こうしないと危険」という場合には、事例と対策は同じ記事の中に載せるべきだと思います。

use Encode; # to secure your imput

Dan the Vulnerable Blogger


この記事へのトラックバックURL

この記事へのトラックバック
駄目です。 [を] Perl の utf8 まわりのおまじない最近良く使うおまじない、というかイディオム。 utf8::decode($text) unless utf8::is_utf8($text); こういう場合は、Encode::decode_utf8()でないと。
#perl - utf8::decode()ではなくEncode::decode_utf8()を使うべき理由【404 Blog Not Found】at 2009年09月13日 13:03
この記事へのコメント
動きました。やっぱ動かしながら学のが楽しいです。

FB_PERLQQ ってモード指定するものなんですね。
http://www.kt.rim.or.jp/~kbk/perl-5.8/encode.html
'適当な文字の例'だと勘違いしていました。
すいません。もっと勉強します。


> utf8 "\xC0" does not map to Unicode at ...
この出力例は一番目のスクリプトの真下にあるのですが、
二番目のスクリプト実効したときのものですね。
読み替えるのは大変なので、できれば移動して欲しいです。
Posted by edry(えどりぃ) at 2009年03月04日 18:39
各位、

「冗長なUTF-8符号化問題」サンプルコードでを微修正しました。
具体的には qw(:fallback_all) を指定している後者では Encode:: を省き、
していないものには付けています。
なお、このサンプルコード、Codepad では違う挙動をします。あそこの Perl は Version 5.8.0 と古過ぎるので。

Dan the Vulnerable
Posted by at 2009年03月04日 15:24
誤:print encode( 'ascii', $utf8, FB_PERLQQ ), "\n";
正:print encode( 'ascii', $utf8, Encode::FB_PERLQQ ), "\n";

エラー指定をするスクリプトの方はちゃんと Encode:: 付いてるのに……
Posted by Yuichirou at 2009年03月04日 12:20
お試しスクリプトが perl v5.8.8 でうまく動かないので、
- print encode( 'ascii', $utf8, FB_PERLQQ ), "\n";
+ print encode( 'ascii', $utf8, 'FB_PERLQQ' ), "\n";
と変更して実行できましたが
/
??
???
????
となって
/
\x{fffd}\x{fffd}
\x{fffd}\x{fffd}\x{fffd}
\x{fffd}\x{fffd}\x{fffd}\x{fffd}
にならないの。なんでだろう。だれか教えてください。


> utf8 "\xC0" does not map to Unicode at ...
これはエラー指定をするスクリプトの方で確認できました。
スクリプトと出力結果の順番がなんか変な感じかも。
Posted by edry(えどりぃ) at 2009年03月03日 22:27
筆者です。コメントありがとうございます。
文字コード関連だけで5回使う連載ですので、「具体的な対策」はおいおい出て参ります。長い目でお待ちいただければと希望します
Posted by 徳丸浩 at 2009年03月03日 20:38
誤:use Encode; # to secure your imput
正:use Encode; # to secure your input

この脆弱性への対策として、Firefoxの自動スペルチェック機能を使用するという手もありますが、日本語に対応していないという問題があります。use Encode並みに有効な手はなさそうですね……
Posted by Yuichirou at 2009年03月03日 20:17