「大きな傘」(BigUmbrella)XSS対策システムの第3回。最終回のつもりでしたが、まだひっぱります。
前回では、コンセプトだけの簡単なコードを示しましたが、それだと以下のような問題が考えられます。
(A) text/html 以外のContent-Typeに対しても、XSSチェック処理が入ってしまう。
例えば、POSTでリクエストされた通りに、text/plain で出力するアプリケーション。この場合、htmlspecialchars()をかける方がおかしいわけですが、正しいはずの処理が、"XSS Found" で潰されてしまいます。
(B) 巨大なファイル転送処理についても、出力フィルターがかかる
一見(A)に似ていますが、こちらはパフォーマンス上の問題点です。数百Mbyteのファイルを、phpで転送する、という場合、出力フィルターがかかると、その巨大なファイルをいったんすべてメモリに取り込んでからの処理となってしまうため、サーバ負荷が極端に上昇します。つまり、DoS攻撃に対して弱くなる、と言えます。
(C) text/html 出力で、GET/POSTされた文字列をそのまま表示したいケースはある
いわゆる「HTML許可」投稿におけるプレビューがそれに該当します。
このうち、(A)は比較的まっとうに対応が可能です。出力フィルター側で、headers_list()関数を使えば、Content-Typeが明示的に指定されているかどうかを確認できます。Content-Typeとしてtext/html が指定されているか、Content-Typeが指定されていない時に限れば、問題解決でしょう。ただし、headers_list()関数は、PHP5以降でしか利用できないため、PHP4でこれを実現することはほぼ不可能であり、(B)と同じ解決法を選択せざるを得ないでしょう。
一方、(B)の問題を、BigUmbrella側だけで対応するのは不可能です。というのも、出力フィルター関数に処理が移ってきた時点で、負荷が問題となる出力のメモリー取り込み処理は終了しているからです。
だから、ユーザスクリプト側で、巨大なファイルを転送する時などに、明示的に出力フィルターを切るしかないでしょう。
// remove output bufferings
while( ob_get_level() ) {
ob_end_clean() ;
}
define( 'BIGUMBRELLA_DISABLED' , true ) ;
<?php
function bigumbrella_init() {
foreach( $_GET as $key => $val ) {
if( preg_match( '/[<\'"].{15}/s' , $val , $regs ) ) {
$GLOBALS['bigumbrella_doubtfuls'][] = $regs[0] ;
}
}
foreach( $_POST as $key => $val ) {
if( preg_match( '/[<\'"].{15}/s' , $val , $regs ) ) {
$GLOBALS['bigumbrella_doubtfuls'][] = $regs[0] ;
}
}
if( ! empty( $GLOBALS['bigumbrella_doubtfuls'] ) ) {
ob_start( 'bigumbrella_outputcheck' ) ;
}
}
function bigumbrella_outputcheck( $s ) {
if( defined( 'BIGUMBRELLA_DISABLED' ) ) return $s ;
if( function_exists( 'headers_list' ) ) {
foreach( headers_list() as $header ) {
if( stristr( $header , 'Content-Type:' ) && ! stristr( $header , 'text/html' ) ) {
return $s ;
}
}
}
if( ! is_array( @$GLOBALS['bigumbrella_doubtfuls'] ) ) {
return "bigumbrella injection found." ;
}
foreach( $GLOBALS['bigumbrella_doubtfuls'] as $doubtful ) {
if( strstr( $s , $doubtful ) ) {
return "XSS found." ;
}
}
return $s ;
}
$GLOBALS['bigumbrella_doubtfuls'] = array() ;
bigumbrella_init() ;
?>