Фильтрация входящих данных
В Москве прошёл митап, на котором Евгений Борисов осветил аспект безопасности MODX. Его доклад можно найти в сообществе. И вот, прочитав его, некоторые разработчики (в том числе и я), обнаружили, что чересчур доверяли MODX в плане фильтрации данных. Правда некоторые вообще ничего об этом не знают, но это их не оправдывает. В общем, одна из проблем фильтрации, которую он показал, работает на большинстве сайтов. А вот почему, давайте пробежимся.
Когда в обычном режиме поступает запрос, MODX запускает метод protect, который фильтрует некоторые данные запроса ($_SERVER["HTTP_USER_AGENT"]
, $_SERVER["HTTP_REFERER"]
, $_SERVER["QUERY_STRING"]
). Дальше запрос передаётся классу modRequest
, который фильтрует входящие данные в массивах $_GET
, $_POST
, $_REQUEST
и $_COOKIE
. Для фильтра он использует вот такой массив:
$sanitizePatterns = array( 'scripts' => '@<script[^>]*?>.*?</script>@si', 'entities' => '@(\d+);@', 'tags1' => '@\[\[(.*?)\]\]@si', 'tags2' => '@(\[\[|\]\])@si', );
Как видим, из запроса вырезаются тег script
, коды символов и теги MODX. Т.е. если указать в запросе тег [[someTag]]
, то он в массиве данных будет удалён. Но Евгений показал, как этот фильтр можно легко обойти. Достаточно добавить теги HTML.
[<b></b>[someTag]<b></b>]
Такая строка под регулярку не попадает. И если эти данные выводятся на странице, то, как говорится, «Милости просим». И это как бы не косяк MODX. Хотя к настройке allow_tags_in_post
можно было бы добавить настройку для вырезания тегов HTML из запросов. Но пока решение этой задачи нужно будет взять в свои руки. Для этого придётся создать плагин и в нём самостоятельно фильтровать данные.
Первое, что можно сделать — удалить теги HTML. В этом случае Женин пример уже работать не будет. MODX сам справится и удалит свои теги из запроса. Второй вариант — кодировать скобки как советовал Евгений. В этом случае MODX их, конечно, не вырежет, но и парсить не будет.
Лично я выбрал оба варианта. Для удобства я добавил пару функций в библиотеку modHelpers
— tag_encode()
и tag_decode()
, которые преобразуют и квадратные и фигурные скобки в соответствующие коды. Вот как это выглядит:
class webListener extends Middlewares\Listener { public function OnMODXInit($before=true) { // Фильтруем скобки foreach ($_REQUEST as $key => &$value) { if (is_string($value)) { $value = tag_encode(strip_tags($value)); // Новая функция modHelpers } } foreach ($_POST as $key => &$value) { if (is_string($value)) { $value = tag_encode(strip_tags($value)); // Новая функция modHelpers } } foreach ($_GET as $key => &$value) { if (is_string($value)) { $value = tag_encode(strip_tags($value)); // Новая функция modHelpers } } foreach ($_COOKIE as $key => &$value) { if (is_string($value)) { $value = tag_encode(strip_tags($value)); // Новая функция modHelpers } } } public function OnWebPagePrerender() { // Чтобы на странице выводились не коды, возвращаем скобки. $output = &$this->modx->resource->_output; $output = htmlspecialchars_decode($output); } }
Как видите, я использую компонент Middlewares вместо плагинов, но вы можете сделать классический плагин на события с названиями методов. Только у события OnMODXInit поставьте отрицательный приоритет, чтобы выполнялся раньше других плагинов.
Что важно ещё отметить. Я в предыдущей статье призывал разработчиков отказаться от использования массива $_REQUEST
. Но многие разработчики игнорируют данную рекомендацию, чем упрощают жизнь всяким хулиганам (ибо вместо POST запроса можно слать GET, что проще). И поэтому не забывайте фильтровать все массивы с данными. Давайте я даже это выделю.
Важно!
Кроме $_GET
и $_POST
массивов обязательно фильтруйте и $_REQUEST
!
Заключение
Если вы всё это будете фильтровать, то и все дополнения будут работать корректно. Но авторам дополнений, по-любому, нужно включать фильтрацию, а не полагаться на разработчика сайта.
И ещё нужно знать, что в API режиме (define('MODX_API_MODE', true)
) обработчик запроса (класс modRequest) не запускается. Поэтому и описанной выше стандартной фильтрации MODX не будет.
П.С. Себе я новые функции modHelpers
уже добавил, а пакет выпущу чуть позднее, так как нужно не только русскую документацию добавить, но и буржуйскую.
Комментарии ()
Вы должны авторизоваться, чтобы оставлять комментарии.
Не вижу, что-то дополнено?
Видимо глюк рсс.
Поскольку не очень силен в php и недавно познакомился с MODX, не совсем понятно как это реализовать на практике. Если не затруднит, можешь привести примеры реализации с плагином и с использованием компонента Middlewares.
Думаю для таких как я, новичков в MODX, будет очень полезно это узнать, да и полагаю, что количество использующих твои компоненты, в том числе modHelpers, возрастет.
И поясни, таким способом будут фильтроваться все входящие данные, или еще что-то дополнительно нужно делать?
Заранее, благодарю!
А в ответе на мой вопрос рекомендуешь создать плагин на событие OnHandleRequest, которое идет, как я понимаю, сразу за событием OnMODXInit
Так как все-таки правильнее?
2. В статье есть такая функция:
Нудна ли она в плагине?
Например, сделал так:
Если это правильно, то на событиеOnWebPagePrerender тоже ставить "-1000"?
Если неправильно, то подскажи как правильно.
Заранее, благодарю!
OnHandleRequest срабатывает только в обычном режиме. Для FormIt, по-моему, сработают оба события. Поэтому для тебя без разницы.
Событие OnWebPagePrerender тоже можно включить. Приоритет не важен.
Пробовал и OnHandleRequest и OnMODXInit.
С событием OnHandleRequest плагин вообще не срабатывает, как в твоем варианте, так и в моем.
С OnMODXInit работает, как в твоем, так и в моем варианте, НО:o
если отправить форму, а потом в админке сохранить изменения в ЛЮБОМ плагине, а после перезагрузить эту страницу админки, или перейти на другую страницу админки, то весь код из сохраненного плагина удаляется, остается только
при этом в журнал летит ошибка:
Чтобы вставить код обратно в тот плагин, приходится перейти в плагин фильтрации, нажать кнопку «Сохранить», при этом код из плагина фильтрации также удаляется, после надо почистить кэш (удаляю папку с кэшем), после вставить опять код в плагин фильтрации, сохранить и только после этого становиться возможным вставить и сохранить код в первом плагине (по другому, если вставлять, то после сохранения код опять пропадает и летит еще одна указанная ошибка).
Пропадает код также и в плагине фильтрации, если после отправки формы нажать в админке в плагине фильтрации кнопку «Сохранить»:o
Как-то так, если ничего не упустил.
Не подскажешь в чем может быть проблема?
Без понятия. Видимо что-то сломал.
На двух сайтах проверил, поведение плагина одно и тоже. Может код для плагина должен быть немного другой, может что-то упустил?
Добавь в начале плагина
Да, так вроде все работает и не удаляется код. Проверю еще на других сайтах, отпишусь.
Огромное спасибо за твою работу по улучшению MODX и за оперативную помощь лично мне!
Здесь вообще, создал плагин с событием OnMODXInit, выставил приоритет "-1000", вставил твой код, сохранил и сайт сломался — ошибка 500 на всем сайте, в том числе в админке.
Чтобы сайт заработал, пришлось из базы удалять код плагина и удалять папку cache из core.
Плагин с событием OnHandleRequest ничего ни на одном сайте не ломает, но и НЕ работает, скобки на коды не меняет.
Может кроме библиотеки modHelpers еще что-то твое нужно установить?
Ошибку можно посмотреть в логах сервера.
На будущее. Достаточно было удалить код в файле плагина в папке core/cache/includes/elements/plugin/id.cache.php, где id — это id плагина.
Подскажи еще следующее.
Например, входящие данные такие:
С помощью первой части плагина $_GET, $_POST, $_REQUEST и $_COOKIE фильтруются от MODX тегов (плейсхолдеров), скобки заменяются на коды.
а с помощью вот этой части:
возвращаются скобки и на сайте отображается плейсхолдер вот в таком виде — [[*pagetitle]] или выводится его содержимое (значение)?
Думаю, что в таком виде — [[*pagetitle]].
Например, в комментариях, если ввести
То должно вывестись — [[*pagetitle]], а не значение (содержимое) этого плейсхолдера.
Правильно я понимаю?
К сожалению комментарии пока не сделал и проверить не могу, поэтому спрашиваю.
Может еще как-то можно проверить?
Поэтому один раз нужно декодировать. Но это по необходимости.