PEAK XOOPS - anti-XSS system (3) in englishin japanese

Archive | RSS |
PHP
PHP : anti-XSS system (3)
Poster : GIJOE on 2006-06-22 12:46:44 (10248 reads)

in englishin japanese
「大きな傘」(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() ;
	}


最後に残った(C)を解決するには、BigUmbrellaとユーザスクリプトの協調が不可欠です。
外部からのInjectionを避けるためには、定数を利用するのがベストでしょう。

ユーザスクリプト側で、あえてBigUmbrellaを切りたいときには、以下のように指定します。

	define( 'BIGUMBRELLA_DISABLED' , true ) ;

あくまで、BigUmbrellaを通過しないのが、「特例」である、ということです。

そしてその特例を通すためには、そのプレビューページに、リファラーやチケットといった、CSRFと同じ対策が必要です。もしそれがないと、外部サイトからXSSで飛んできて(この時点では、XMLHttpRequestは利用不可)、プレビューページへPOSTした表示内容がXSS。後者(プレビューページ)のXSSはサイト内ですから、あとはどこへでもXMLHttpRequestを打ち放題、となるわけです。

このあたり、頭がこんがらがってしまう人も多そうですが、XMLHttpRequestは同一ホストにしか飛ばせないので、サイト全体がXSS対策されている以上、リファラー偽装・チケットの別途取得といったXMLHttpRequest特有のテクニックが使えず、結果的に、CSRF対策がXSS対策にもなる、というわけです。

とりあえず、下に改良版コードを載せます。(A)と(C)に対応するコードを追加し、さらに、REQUESTをGETとPOSTに分離しています。COOKIEやSERVER変数については、(4)で検討します。


<?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() ;
?>

0 comments

Related articles
Printer friendly page Send this story to a friend

Comments list

Login
Username or e-mail:

Password:

Remember Me

Lost Password?

Register now!