Начну с небольшого отступления.
И все таки правильно говорят, а на некторых форумах (особенно UNIX-овых), прямо кричат — RTFM! Кто не понял очем идет речь — RTFM в переводе означает «читай эту чертову документацию!». Это все я собственно вот к чему: изучая и что-то пытаясь написать на фреймворке Yii, возникла задача фильтрации входных данных от различного рода «зловредных» символов (аля XSS-атака) и первое что пришло в голову — это написать свой фильтр (что я все таки и сделал), однако creocoder, на форуме Yii , совершенно спрпаведливо заметил, что не зачем изобретать велосипед, все уже есть готовое, необходимо только RTFM! Речь шла о классе CHtmlPurifier, который является оберткой для библиотеки HTML Purifier , и выполняет все те функции, которые мне необходимы (правда я так и не попробывал его в действии, может и зря конечно). Но раз уж я начал писать свой фильтр — решил все таки это дело завершить, да и просто написать статью о фильтрах в Yii.
И так!
Фильтры — фрагменты кода, которые могут быть выполнены до и\или после выполнения экшена контроллера. Фильтры, при необходимости, могут не допустить выполнения запрошенного экшена.
Фильтры могут быть как методами текущего контроллера, так и отдельными классами — что позволяет повторно их использовать. Если фильтр реализуется как метод класса, он должен иметь префикс «filter».
Пример:
public function filterAccessControl()
{
.......
}
Фильтр, реализованный в виде отдельного класса, должен быть наследником класса CFilter.
Пример:
class XssFilter extends CFilter
{
// код который выполнится до выполнения экшена
public function preFilter()
{
.......
}
// код который выполнится после выполнения экшена
public function postFilter()
{
.......
}
}
Для активации фильтров, необходимо в контроллере переопределить метод filters,который должен вернуть массив всех фильтров для данного контроллера (или его отдельных экшенов).
Пример:
public function filters()
{
return array(
'accessControl',
array(
'application.filters.XssFilter',
'clean' => 'all'
)
);
}
В этом примере ‘accessControl’ — фильтр, реализованный как метод контроллера, а ‘application.filters.XssFilter’ — фильтр, реализованный в виде отдельного класса, который хранится в каталоге /protected/filters/. ‘clean’ — устанавливаем свойство фильтра.
Это была краткая справка по фильтрам в Yii, более подробно можно почитать тут.
Теперь непосредственно приступим к реализации нашего фильтра. Функцию очистки данных, которая и выполняет всю работу — я взял из фреймворка Kohana. Ну на этом достаточно слов, приведу сам код фильтра — он совсем простой, так что думаю проблем быть не должно.
/**
* @author Opeykin A. <opeykin.ru&; <aopeykin@gmail.com>
* @version 0.0.1
* @package filters
*
* Фильтр предназначен для фильтрации входных данных, c целью предотвратить xss атаки.
* Для фильтрации используются регулярные выражения из фреймворка Kohana 2.3.1
* @example
*
* public function filters()
* {
* return array(
* array('application.filters.XssFilter',
* 'clean' =>; 'all'
* )
* );
*
* }
*
* В качетве параметра 'clean' могут быть:
* - 'all' - фильтруются GET,POST,COOKIE,FILES массивы;
* - '*' - аналог ALL;
* - так же возможно сочетание любых из параметров, например GET,COOKIE или POST,FILES
*/
class XssFilter extends CFilter
{
public $clean = 'all';
protected function preFilter($filterChain)
{
$this->clean = trim(strtoupper($this->clean));
$data = array(
'GET' =>&$_GET,
'POST' =>&$_POST,
'COOKIE' =>&$_COOKIE,
'FILES' =>&$_FILES
);
if($this->clean === 'ALL' || $this->clean === '*')
{
$this->clean = 'GET,POST,COOKIE,FILES';
}
$dataForClean = split(',',$this->clean);
if(count($dataForClean))
{
foreach ($dataForClean as $key => $value)
{
if(isset ($data[$value]) && count($data[$value]))
{
$this->doXssClean($data[$value]);
}
}
}
return true;
}
protected function postFilter($filterChain)
{
// logic being applied after the action is executed
}
private function doXssClean(&$data)
{
if(is_array($data) && count($data))
{
foreach($data as $k => $v)
{
$data[$k] = $this->doXssClean($v);
}
return $data;
}
if(trim($data) === '')
{
return $data;
}
// xss_clean function from Kohana framework 2.3.1
$data = str_replace(array('&','<','>'), array('&amp;','&lt;','&gt;'), $data);
$data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
$data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
// Remove any attribute starting with "on" or xmlns
$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
// Remove javascript: and vbscript: protocols
$data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
// Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
// Remove namespaced elements (we do not need them)
$data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
do
{
// Remove really unwanted tags
$old_data = $data;
$data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
}
while ($old_data !== $data);
return $data;
}
}
Я совсем немного протестировал это фильтр — на первый взгляд — все работает!
Любые замечания и комментарии приветствуются!
Хочу добавить свои замечания к реализации фильтров в Yii…
Мне кажется было удобно иметь метод, который вызывается перед выполнением preFilter и postFilter, например init() — который выполняет инициализацию фильтра, при этом в нем должны быть доступны параметры, передавемые в фильтр из контроллера (по этой причине невозможно использовать __construct). Конечно можно расширить CFilter для этих целей, но «родная» возможность сделать это была бы лучшим вариантом.
Надеюсь, описанный материал окажется полезен!
Ищите разработчиков на Yii ?
Мы решим Ваши задачи!
Скачать xssfilter