Русское сообщество разработки на PHP-фреймворке Laravel.
Ты не вошёл. Вход тут.
Страницы 1
Добрый день, по умолчанию в Laravel присутствует защита от SQL инькций, но воизбежание XSS приходиться переодически использовать HTML::entities(), удивительно что автоматическая защита не является опцией. Имели ли вы опыт расширения стандартного Input, и что из этого получилось? Признаюсь я на скорую руку поступил так:
class Input extends Laravel\Input {
public static function get($key = null, $default = null) {
$value = parent::get($key = null, $default = null);
return is_array($value) ? $value : HTML::entities($value);
}
public static function all() {
$input = parent::all();
foreach($input as $key => $value) {
$input[$key] = HTML::entities($value); //даже не array_map =)
}
return $input;
}
}
Теперь задумался о более ответственном подходе, и вот пришел советоваться.
Не в сети
- Имели ли вы опыт расширения стандартного Input, и что из этого получилось?
Такой подход — не самый верный, ИМХО. В PHP 4 с подобной целью использовали magic_quotes_gpc — правда, они служили для предотвращения SQL-инъекций. Но экранировать весь ввод — это логически неверно. Предположим, тебе надо вывести предыдущее значение поля формы — для этого нужно уже не просто сделать PHPInput::get()
, а дополнительно декодировать HTML — который тот же PHPInput::get()
предусмотрительно добавил. С magic_quotes_gpc даже на production-сайтах было не редкостью увидеть, как при отправке формы с ошибками она показывалась вновь, но во всех её строках перед апострофами появлялись косые черты (I'm here → I\'m here).
Получается много лишних вызовов и вообще, экранировать надо при использовании данных в каждом конкретном месте, а не всё подряд на входе, иначе запутаешься, что идёт от пользователя и где данные экранированы, а что идёт изнутри приложения и где обратно преобразовывать HTML не нужно. В итоге будут непонятные повреждения строк — где-то лишние сущности, где-то наоборот таинственно исчезнувшие
xml"
.
Конкретно с экранирование HTML при выводе для защиты от XSS всё просто, если весь вывод производить только в шаблонах (чего я рекомендую придерживаться во всех случаях без исключений). В шаблоне все данные идут «изнутри» приложения, поэтому варианта два:
xml<input value="{{ e($value) }}">
PHP{{ HTML::input('name', $value) }}
Мне лично ближе второй подход. В Laravel есть стандартный класс HTML (и Form, но это немного другое), но для меня он не очень функциональный, поэтому я использую свой. Он — часть моего пакета, но работает и сам по себе. Если интересно, вот инструкция:
Код самодокументирующийся и есть комментарии. Главная возможность — в том что можно выводить любой тег с автоматическим экранированием и/или не выводить, если содержимое пустое. Формат вызова:
HLEx::tagName[_q][_if] ('content', array('attr1' => ...));
Первый параметр — содержимое, второй (если передан) — либо строка (атрибут class), либо массив атрибутов. Если атрибут имеет значение null (но не пустая строка, если это не style или class) — он не выводится (см. последний пример ниже).
Если есть _q — содержимое экранируется, если есть _if — тег выводится только если trim($content) !== ''. Естественно, и _q, и _if могут быть опущены или может быть передан только один из них.
Атрибуты всегда автоматически экранируются.
HLEx::span_q_if('abc', 'class1 cl2'); //=> <span class="class1 cl2">abc</span>
HLEx::span_q_if('abc', array('class' => 'class1 cl2')); // идентично предыдущему
HLEx::span_q_if('abc', array('id' => 'x')); //=> <span id="x">abc</span>
HLEx::span_q_if('<abc>', 'class1 cl2'); //=> <span class="class1 cl2"><abc></span>
HLEx::span_q_if('', 'class1 cl2'); //=> ''
HLEx::span_if('<abc>', 'class1 cl2'); //=> <span class="class1 cl2"><abc></span>
HLEx::span_q('', 'class1 cl2'); //=> <span class="class1 cl2"></span>
HLEx::span('<abc>', 'class1 cl2'); //=> ''
$url = 'http://google.com';
HLEx::a('link', array('target' => HLEx::target($url), 'href' => $url));
//=> <a href="http://google.com" target="_blank">link</a>
$url = 'local/path';
HLEx::a('link', array('target' => HLEx::target($url), 'href' => $url));
//=> <a href="local/path">link</a>
Кроме этого псевдометода там есть другие методы для вывода различных тегов —
xml<optgroup>
,
xml<input type="radio">
и т.п. Также есть методы для быстрого построения таблиц из массивов (visualize и table2D) — полезно в отладке.
Не в сети
Вы провели отличную работу, и уверен вам удобно работать. Но я думаю, я сам должен прийти к таким решениям, ориентируясь на свои потребности. Но все таки, относительно экранирования только во вью. Как мне спать по ночам, знаю что непосредственно в БД у меня могут находиться XSS в боевой готовности. И единственная причина их несрабатывания, это безопасный вью. Неужели экранировать перед помещением в БД, являеться излишним? Возможно разумнее перекрывать даже не Input, а Eloquent. В общем буду думать, искать.
Не в сети
Возможно, моё сообщение звучало слишком категоричным, но я не собирался навязывать что-либо.
- Как мне спать по ночам, знаю что непосредственно в БД у меня могут находиться XSS в боевой готовности.
Вопрос не совсем в этом. «HTML» — это просто форма записи строк; в каких-то местах (переменных, полях БД и т.п.) вполне приемлемы символы вроде < и &. Я говорил лишь об этом.
Например, если в таблице есть два поля: html и source, где в первом — отформатированное сообщение, а во втором — его исходный код (в BB-кодах, например), то в html сущности должны быть экранированы, а в source — нет. Поэтому при выводе source в форму редактирования сообщения придётся в любом случае экранировать сущности повторно — либо, если хранить в source уже экранированный HTML, то при переформатировании сообщения придётся вначале убрать экранировку, отформатировать, а затем — вернуть экранировку. Слишком много лишних действий.
Безопасные шаблоны должны быть в любом случае, так как данные из Input или Eloquent — не единственные источники XSS. Скажем, интернет-магазин может синхронизироваться из внешнего файла CSV или XLS — и там тоже могут быть опасные символы. И наоборот, картинка может приходить от пользователя через стандартный ввод, и экранируя в ней HTML ты разрушаешь данные, причём из-за двоичной природы обратное преобразование их не восстановит.
На самом деле проще как раз таки контролировать единый канал вывода, а не множество каналов ввода. В шаблоне ты изначально сфокусирован на выводе, поэтому очень просто добавлять экраны в нужных местах. А вот помнить, где может быть потенциально опасный ввод на протяжении тысяч строк кода всего приложения — намного сложнее.
Не в сети
На самом деле проще как раз таки контролировать единый канал вывода, а не множество каналов ввода. В шаблоне ты изначально сфокусирован на выводе, поэтому очень просто добавлять экраны в нужных местах. А вот помнить, где может быть потенциально опасный ввод на протяжении тысяч строк кода всего приложения — намного сложнее.
Да, вы правы, звучит логично. Думаю стоит пересмотреть свой подход. Спасибо!
Я пересмотрел свой класс, то что я в начале написал неверно. Так лучше:
class Input extends Laravel\Input {
public static function get($key = null, $default = null) {
$value = parent::get($key, $default);
return is_array($value) ? array_map(array('self', 'safe'), $value) : HTML::entities($value);
}
private static function safe($value) {
return HTML::entities($value);
}
}
Не в сети
- Так лучше:
Он по-прежнему не обрабатывает вложенные массивы:
public static function get($key = null, $default = null) {
return static::safe( parent::get($key, $default) );
}
private static function safe($value) {
return is_array($value) ? array_map(array('self', __FUNCTION__), $value) : HTML::entities($value);
}
Не в сети
Не в сети
Страницы 1