Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 18.02.2013 04:54:51

Ikeaboy
Откуда: Киев
Сообщений: 28

Безопасный Input

Добрый день, по умолчанию в Laravel присутствует защита от SQL инькций, но воизбежание XSS приходиться переодически использовать HTML::entities(), удивительно что автоматическая защита не является опцией. Имели ли вы опыт расширения стандартного Input, и что из этого получилось? Признаюсь я на скорую руку поступил так:

PHP
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;
    }

}

Теперь задумался о более ответственном подходе, и вот пришел советоваться.

Не в сети

#2 18.02.2013 15:41:51

Re: Безопасный Input

  1. Имели ли вы опыт расширения стандартного Input, и что из этого получилось?

Такой подход — не самый верный, ИМХО. В PHP 4 с подобной целью использовали magic_quotes_gpc — правда, они служили для предотвращения SQL-инъекций. Но экранировать весь ввод — это логически неверно. Предположим, тебе надо вывести предыдущее значение поля формы — для этого нужно уже не просто сделать PHPInput::get(), а дополнительно декодировать HTML — который тот же PHPInput::get() предусмотрительно добавил. С magic_quotes_gpc даже на production-сайтах было не редкостью увидеть, как при отправке формы с ошибками она показывалась вновь, но во всех её строках перед апострофами появлялись косые черты (I'm hereI\'m here).

Получается много лишних вызовов и вообще, экранировать надо при использовании данных в каждом конкретном месте, а не всё подряд на входе, иначе запутаешься, что идёт от пользователя и где данные экранированы, а что идёт изнутри приложения и где обратно преобразовывать HTML не нужно. В итоге будут непонятные повреждения строк — где-то лишние сущности, где-то наоборот таинственно исчезнувшие xml".

Конкретно с экранирование HTML при выводе для защиты от XSS всё просто, если весь вывод производить только в шаблонах (чего я рекомендую придерживаться во всех случаях без исключений). В шаблоне все данные идут «изнутри» приложения, поэтому варианта два:

  1. Вставлять в код вызовы htmlspecialchars напрямую: xml<input value="{{ e($value) }}">
  2. Использовать классы, генерирующие HTML и экранирующие данные по мере надобности: PHP{{ HTML::input('name'$value) }}


Мне лично ближе второй подход. В Laravel есть стандартный класс HTMLForm, но это немного другое), но для меня он не очень функциональный, поэтому я использую свой. Он — часть моего пакета, но работает и сам по себе. Если интересно, вот инструкция:

  1. Сохранить код в application/libraries/Px/HLEx.php.
  2. Можно сделать алиас в application/config/application.php — HLEx → Px\HLEx.

Код самодокументирующийся и есть комментарии. Главная возможность — в том что можно выводить любой тег с автоматическим экранированием и/или не выводить, если содержимое пустое. Формат вызова:

PHP
HLEx::tagName[_q][_if] ('content', array('attr1' => ...));

Первый параметр — содержимое, второй (если передан) — либо строка (атрибут class), либо массив атрибутов. Если атрибут имеет значение null (но не пустая строка, если это не style или class) — он не выводится (см. последний пример ниже).

Если есть _q — содержимое экранируется, если есть _if — тег выводится только если trim($content) !== ''. Естественно, и _q, и _if могут быть опущены или может быть передан только один из них.

Атрибуты всегда автоматически экранируются.

Примеры:

PHP
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">&lt;abc&gt;</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) — полезно в отладке.

Не в сети

#3 18.02.2013 19:37:24

Ikeaboy
Откуда: Киев
Сообщений: 28

Re: Безопасный Input

Вы провели отличную работу, и уверен вам удобно работать. Но я думаю, я сам должен прийти к таким решениям, ориентируясь на свои потребности. Но все таки, относительно экранирования только во вью. Как мне спать по ночам, знаю что непосредственно в БД у меня могут находиться XSS в боевой готовности. И единственная причина их несрабатывания, это безопасный вью. Неужели экранировать перед помещением в БД, являеться излишним? Возможно разумнее перекрывать даже не Input, а Eloquent. В общем буду думать, искать.

Большое спасибо за консультацию!

Не в сети

#4 18.02.2013 22:31:39

Re: Безопасный Input

Возможно, моё сообщение звучало слишком категоричным, но я не собирался навязывать что-либо.

  1. Как мне спать по ночам, знаю что непосредственно в БД у меня могут находиться XSS в боевой готовности.

Вопрос не совсем в этом. «HTML» — это просто форма записи строк; в каких-то местах (переменных, полях БД и т.п.) вполне приемлемы символы вроде < и &. Я говорил лишь об этом.

Например, если в таблице есть два поля: html и source, где в первом — отформатированное сообщение, а во втором — его исходный код (в BB-кодах, например), то в html сущности должны быть экранированы, а в source — нет. Поэтому при выводе source в форму редактирования сообщения придётся в любом случае экранировать сущности повторно — либо, если хранить в source уже экранированный HTML, то при переформатировании сообщения придётся вначале убрать экранировку, отформатировать, а затем — вернуть экранировку. Слишком много лишних действий.

Безопасные шаблоны должны быть в любом случае, так как данные из Input или Eloquent — не единственные источники XSS. Скажем, интернет-магазин может синхронизироваться из внешнего файла CSV или XLS — и там тоже могут быть опасные символы. И наоборот, картинка может приходить от пользователя через стандартный ввод, и экранируя в ней HTML ты разрушаешь данные, причём из-за двоичной природы обратное преобразование их не восстановит.

На самом деле проще как раз таки контролировать единый канал вывода, а не множество каналов ввода. В шаблоне ты изначально сфокусирован на выводе, поэтому очень просто добавлять экраны в нужных местах. А вот помнить, где может быть потенциально опасный ввод на протяжении тысяч строк кода всего приложения — намного сложнее.

Не в сети

#5 19.02.2013 01:13:57

Ikeaboy
Откуда: Киев
Сообщений: 28

Re: Безопасный Input

На самом деле проще как раз таки контролировать единый канал вывода, а не множество каналов ввода. В шаблоне ты изначально сфокусирован на выводе, поэтому очень просто добавлять экраны в нужных местах. А вот помнить, где может быть потенциально опасный ввод на протяжении тысяч строк кода всего приложения — намного сложнее.

Да, вы правы, звучит логично. Думаю стоит пересмотреть свой подход. Спасибо!

Я пересмотрел свой класс, то что я в начале написал неверно. Так лучше:

PHP
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);
    }

}

Не в сети

#6 19.02.2013 09:54:05

Re: Безопасный Input

  1. Так лучше:

Он по-прежнему не обрабатывает вложенные массивы:

PHP
    public static function get($key null$default null) {
        return static::
safeparent::get($key$default) );
    }

    private static function 
safe($value) {
        return 
is_array($value) ? array_map(array('self'__FUNCTION__), $value) : HTML::entities($value);
    }

Не в сети

#7 19.02.2013 13:52:29

Ikeaboy
Откуда: Киев
Сообщений: 28

Re: Безопасный Input

Ох, красиво! О вложенных массивах не подумал. Спасибо!

Не в сети

Подвал раздела