Знаете как обычно происходит работа с данными пользователя в веб-приложении?
Показать форму для пользователя → получить данные → обработать и сохранить (например в БД) → в дальнейшем показать пользователю в нужном виде или в форме редактирования
Просто? Но всё же есть мелкие нюансы и на волне, что в последнее время я столкнулся с тоннами чужого кода и новым шаблонизатором Twig
www.twig-project.org/ который желает всё и вся обезопасить перед выводом пользователю, я бы хотел добавить своих 5 копеек в общую копилку размышлений.
С детства нам прививают ряд жизненных правил, манеры. Поднимать стульчак мальчикам, здороваться со старшими, переходить дорогу на зеленый свет ну и т. д. На самое главное в нашей кухни это мыть руки перед едой. Так как я был хорошим мальчиком, то обязательно придерживаюсь и сейчас этого правила.
Вот получили мы данные пользователя и хотим сохранить в базе данных, разумеется чтобы избежать всяческих SQL инъекций мы используем экранирование, кто как, но я люблю всякие placeholder'ы, ранее это были встроенные в DBI bind и prepare, теперь это или встроенные возможности ActiveRecord паттерна в CodeIgniter фреймворке или обычные addslashes… Кто конечно пользуется, тот понимает зачем и как. Но самое главное НО по тексту далее, SQL инъекции это еще не все беды, которыми может наградить нас пользователь, еще это могут быть попросту вставленный html код или еще интереснее XSS инъекция, которая каким-либо интересным образом и упирает наши cookies или еще другие пакости строит. Именно о том как мы привыкли предохранятся от этой напасти я бы и хотел поговорить, а именно КОГДА?
В последних тенденциях развития средств веб-разработки (извините, я слежу на данный момент только за PHP и Ruby), так большая часть фреймворков предпочитает работать по принципу: получили данные пользователя → обработали от SQL инъекций и положили в базу, а когда уже надо вывести пользователю в каком-либо виде или в форме, тогда мы и будем обрабатывать от HTML инъекций и JS XSS вставок. Ruby on Rails порадует нас конструкциями вида <%=h title %>, а например CodeIgniter а-ля
<?=form_prep($title)?>
. Ну так вот, эта простейшая функция делает две основных задачи:
$str = str_replace(array("'", '"'), array("'", """), $str);
$str = htmlspecialchars($str);
В принципе тоже самое делала моя Perl'овая функция:
sub safe_html{
my $val = shift;
$val =~ s/\'/'/gm;
$val =~ s/\"/"/gm;
$val =~ s/\&/&/gm;
$val =~ s/\</</gm;
$val =~ s/\>/>/gm;
return $val;
}
Но суть в том, что нас приучают мыть руки после еды!!! Что конечно тоже хорошо, но не заменит мытья рук перед едой как избавление от расстройств желудка! А вдруг мы забудем где-то экранировать например вывод комментария в шаблоне и злоумышленник просто напишет нам отличную XSS инъекцию или огромными буквами напишет слово «ДОМ»? Не стоит надеяться на свою память, что вы не забудете, обязательно забудете это как Законы Мерфи!
Многие это тоже понимают, например создатели различных шаблонизаторов, которые предлагают такие средства как sandbox или «эскейпинг» по умолчанию всего вывода. Хорошо это или плохо, я не могу утверждать, я просто понимаю, что мне не нравится этот подход, когда я ничего не контролирую железно.
Мой подход это именно мыть руки в начале приема пищи, а в нашем случае перед «экскейпингом» SQL инъекций.
Моё решение под CodeIgniter создание хелпера params_helper.php с рядом функций:
/**
* Возвращает параметры отравленные пользователем
*
* @param string $name
* @param bool $clean флаг очистки от XSS
* @param bool $clean_html флаг очистки от html ntujd
* @return mixed
*/
function param( $name, $xss = TRUE, $clean_html = TRUE ){
$CI = &get_instance();
if( is_array($name) ) return $this->params( $name, $xss, $clean_html );
$value = '';
// получаем данные экранируем
$value = $CI->input->post( $name, $xss );
// если нужно экранировать html
if( $clean_html ) $value = htmlspecialchars( $value, ENT_QUOTES );
return $value;
}
/**
* Взять параметры c помощью функции param для целого массива
* по умолчанию фильтруется сразу же XSS и html теги
*
* @param array $params массив названий нужных параметров
* @param bool $xss флаг очистки от XSS
* @param bool $clean_html флаг очистки от html ntujd
* @return array
*/
function params( $params='', $xss = TRUE, $clean_html = TRUE ){
$result = array();
if( empty($params) ) return $result;
if( !is_array($params) ) return $this->param($name, $xss, $clean_html);
foreach($params as $name){
$result[$name] = $this->param($name, $xss, $clean_html);
}
return $result;
}
Теперь каждый наш контролер может получать данные от пользователя безопасно и просто
$title = param( 'title' );
// или $params = param( array('title','author') ); где вернуться параметры
$this->article_model->title = $title; // это так, пример из головы
$this->article_model->save();
Но теперь может случиться одна проблема, что данные будут экранироваться от HTML и XSS по два раза, например в CodeIgniter при выводе в VIEW шаблоне встретится конструкция <?=form_prep($title)?> а так же все функции из form_helper по умолчанию используют form_prep для внутреннего создания элементов html из присланных данных.
В нашем случае с CodeIgniter нам ничего не стоило создать в проекте расширение-замену этой функции своей в файле MY_form_helper.php скопировав код из функции form_prep в наш файл и закоментировав лишь одну строку htmlspecialchars, вот полный код:
if ( ! function_exists('form_prep'))
{
function form_prep($str = '', $field_name = '')
{
static $prepped_fields = array();
// if the field name is an array we do this recursively
if (is_array($str))
{
foreach ($str as $key => $val)
{
$str[$key] = form_prep($val);
}
return $str;
}
if ($str === '')
{
return '';
}
// we've already prepped a field with this name
// @todo need to figure out a way to namespace this so
// that we know the *exact* field and not just one with
// the same name
if (isset($prepped_fields[$field_name]))
{
return $str;
}
// In case htmlspecialchars misses these.
$str = str_replace(array("'", '"'), array("'", """), $str);
// $str = htmlspecialchars($str);
if ($field_name != '')
{
$prepped_fields[$field_name] = $str;
}
return $str;
}
}
Что приводит нас к желаемой логике: помыли руки, покушали, убрали за собой.
p.s. Интересно, а как вы предпочитаете мыть руки, до еды или после? ;-)
Комментарии (8)
RSS свернуть / развернутьК тому же, здесь описан самый простой способ защиты от XSS — запрет на HTML. Но иногда нужно иметь возможность разрешить использовать HTML, но зачищать его от скриптов, которые ведут на другие домены. Тут уже вступает в действие более сложный алгоритм.
В более сложном алгоритме бывают ошибки, которые исправляются. Если обрабатывать данные им до вставки в БД, то соответственно после изменения алгоритма такие данные становятся опять «грязными». Поэтому функция логично отрабатывает при выводе данных пользователю.
akhmetov
MpaK
akhmetov
akhmetov
waitekk
я где-то опечатался?
MpaK
waitekk
MpaK
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.