CodeIgniter ユーザが CodeIgniter の XSS フィルタについて知るべき 5つのこと

(2015/08/20) CodeIgniter 3.0 では XSS フィルタを入力フィルタとして使うことは誤りであり XSS フィルタは出力時にのみ使うべきである旨がユーザガイドに明記されています。http://www.codeigniter.com/user_guide/installation/upgrade_300.html#step-13-check-for-usage-of-the-xss-clean-form-validation-rule

CodeIgniter には、XSS フィルタが標準で用意されています。具体的には、

  • セキュリティクラスの $this->security->xss_clean() メソッド
  • セキュリティヘルパーの xss_clean() 関数

です。また、入力クラスやフォームバリデーションクラスにも XSS フィルタを適用するオプションが用意されています。

XSS フィルタの実装は、現在、セキュリティクラスの xss_clean() メソッドにあります(現在のソースコード)。

1. XSS フィルタは入力フィルタである

この XSS フィルタリング機能は、基本的に、入力時に入力値をフィルタリングするものです。

設定ファイル config.php

$config['global_xss_filtering'] = TRUE;

と指定すると(デフォルトは FALSE)、GET/POST/COOKIE データが自動的にフィルタされます。これをグローバル XSS フィルタリングと呼びます。

2. XSS フィルタはブラックリストである

XSS フィルタは、危険そうな文字列に対して、その文字列を変更したり削除したりします。

以下の「input」に示した文字列を XSS フィルタで処理すると、「output」のようになります。

 input: <body onload=alert('XSS')>
output: &lt;body&gt;

上記では、 タグが文字参照に変更され、onload 属性が削除されています。

ただし、HTML タグが必ず文字参照に変換されるわけではありません。

 input: <>&
output: <>&
 input: <p>abc</p>
output: <p>abc</p>

XSS フィルタは、危険そうな文字列を削除したり置き換えるように実装されており、本質はブラックリストです(ただし、文字参照エスケープされることもあり単純なブラックリストではありません)。

そのため、未知の攻撃方法に対するフィルタ漏れの可能性があります。少なくとも過去に二度以上、XSS フィルタの脆弱性が発見され、修正されています。

(2011/11/30 追記)
CodeIgniter 2.0.3 以前に存在する新しい脆弱性が公開されました。

3. XSS フィルタは文字列を勝手に変更する

文字列を勝手に変更しますので、仕様をよく理解していないと、予期しない文字列の変更を経験することになります。

例えば、<script> タグは [removed] に変更されます。

 input: <script>alert(1)</script>
output: [removed]alert&#40;1&#41;[removed]
 input: <script src="http://ha.ckers.org/xss.html">alert(1)</script>
output: [removed]alert&#40;1&#41;[removed]

また、よくわかりませんが、文字参照の「;」が忘れられた場合に対処するため、& に英数字が 2つ以上続く場合、「;」が追加されます。

 input: EPA&DHA
output: EPA&DHA;

このように、XSS フィルタの仕様は非常に複雑であり、場合により想定外の振る舞いをします。また、<script> タグなど一部の文字列パターンは [removed] に変更されますので、ユーザがそのような文字列をそのまま入力するような場面では全く使用できません。

削除パターンは現在以下が定義されています。

削除される文字列

document.cookie
document.write
parentNode
innerHTML
window.location
-moz-binding
<!--
-->
<![CDATA[

削除される文字列の正規表現

javascript\s*:
expression\s*(\(|&\#40;)
vbscript\s*:
Redirect\s+302

4. XSS フィルタは非常に遅い

手許の環境で以下のような文字列をベンチマークすると、

<img src="http://ha.ckers.org/images/84844372/rsnake/hackers.jpg" onload="alert(1);" />

xss_clean() は htmlspecialchars() と比較して 85倍遅いという結果になりました。

このように XSS フィルタは非常に遅いため、グローバル XSS フィルタリングはデフォルトでオフに設定されています。

ベンチマークのサンプルコード

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Test_xss_clean extends CI_Controller {

	function __construct()
	{
		parent::__construct();
	}
	
	function bm()
	{
		$test = '<img src="http://ha.ckers.org/images/84844372/rsnake/hackers.jpg" onload="alert(1);" />';

		$this->benchmark->mark('htmlspecialchars_start');
		for ($i = 1; $i < 1000000; $i++)
		{
			h($test);
		}
		$this->benchmark->mark('htmlspecialchars_end');
		
		$this->benchmark->mark('xss_clean_start');
		for ($i = 1; $i < 10000; $i++)
		{
			$this->security->xss_clean($test);
		}
		$this->benchmark->mark('xss_clean_end');
		
		$h = $this->benchmark->elapsed_time('htmlspecialchars_start', 'htmlspecialchars_end') / 100;
		$x = $this->benchmark->elapsed_time('xss_clean_start', 'xss_clean_end');
		
		echo 'htmlspecialchars: ' . $h;
		echo ' ';
		echo 'xss_clean: ' . $x;
		echo ' ';
		echo $x / $h;
	}
}


if ( ! function_exists('h'))
{
	function h($var)
	{
		if (is_array($var))
		{
			return array_map('h', $var);
		}
		else
		{
			return htmlspecialchars($var, ENT_QUOTES, config_item('charset'));
		}
	}
}
/* End of file test_xss_clean.php */
/* Location: ./application/controllers/tests/test_xss_clean.php 

5. XSS フィルタは使うべきでない

以上のように、入力データが予期せず変更される可能性があること、XSS を完全に防げない可能性があること、処理が非常に重いことから、XSS フィルタは積極的に使用すべきではありません。

もし、使用する場合は、仕様をよく理解した上で使用してください。なお、グローバル XSS フィルタをオンにすべき状況というのは個人的には想定できません。

なお、CodeIgniter での XSS 対策については、

を参照してください。