• Блог
  • Фильтрация данных

Работая с массивами $_GET и $_POST очень важно не забывать обрабатывать значения. Иначе можно получить неприятный сюрприз в виде разных инъекций. Об этом написано уже много статей. Но тем не менее, все равно встречается код с прямым выводом информации на страницу (возможна XSS инъекция)

<?php
echo $_POST['name'];
?>

или запросом к базе данных (возможна SQL инъекция)

<?php
$name = $_GET['name'];
$query="SELECT * FROM table WHERE name like '$name'";

Такой сайт рано или поздно будет взломан. Чтобы этого избежать важно запомнить главное правило:

Никогда не доверяйте данным, которые приходят от пользователя!

Фильтрация данных

Для того, чтобы обезопасить сайт от различных инъекций, обязательно фильтруйте данные. Давайте рассмотрим несколько способов.

Для начала откажитесь от использования глобальной переменной $_REQUEST. Если вы работаете с данными формы, то обращайтесь к $_POST. Параметры запроса можно получить в $_GET переменной. Не упрощайте задачу злоумышленнику.

Цифровые данные приводите к соответствующему типу:

<?php
$page = (int) $_POST['page'];
echo 'Страница с номером' . $page;

Также фильтровать можно с помощью htmlspecialchars() и filter_input(). Они пригодятся, если нужно подготовить данные для вывода на страницу. А еще, чтобы вырезать все HTML и PHP теги, можно использовать функцию strip_tags().

Функция $modx->stripTags()

В MODX есть функция, которая расширяет php-шную strip_tags(). Она вырезает не только HTML и PHP теги, но MODX теги, а также позволяет дополнительно указать запрещённые паттерны (шаблоны regex).

<?php
public function stripTags($html, $allowed= '', $patterns= array(), $depth= 10) {
    $stripped= strip_tags($html, $allowed);
    if (is_array($patterns)) {
        if (empty($patterns)) {
            $patterns = $this->sanitizePatterns;
        }
        foreach ($patterns as $pattern) {
            $depth = ((integer) $depth ? (integer) $depth : 10);
            $iteration = 1;
            while ($iteration <= $depth && preg_match($pattern, $stripped)) {
                $stripped= preg_replace($pattern, '', $stripped);
                $iteration++;
            }
        }
    }
    return $stripped;
}

Как мы видим из определения функции, это третий аргумент. Если ничего не передать, что функция будет использовать шаблон по-умолчанию $modx->sanitizePatterns. Он представляет собой массив с паттернами для preg_replace():

array(
    'scripts'   => '@<script[^>]*?>.*?</script>@si',  // Вырезает тег script. 
    'entities'  => '@&#(\d+);@',    // вырезает все HTML сущности
    'tags1'     => '@\[\[(.*?)\]\]@si', // непустой тег MODX
    'tags2'     => '@(\[\[|\]\])@si',   // двойные скобки тегов MODX
);

У некоторых может возникнуть вопрос, зачем указывать тег script, если он и так вырезается strip_tags(). Это станет понятно дальше.

Функция $modx->sanitize()

Ещё MODX позволяет отфильтровать сразу массив.

$post = $modx->sanitize($_POST, $modx->sanitizePatterns);
// или так
$post = modX::sanitize($_POST, $modx->sanitizePatterns);

Но в этом методе не выполняется strip_tags(), а значит HTML и PHP теги не вырезаются. Вот тут как раз и пригодится шаблон в массиве $modx->sanitizePatterns для тега script — самого опасного тега для межсайтового скриптинга XSS. А если нужно вырезать теги HTML и PHP, то добавьте их в pattern по аналогии с тегом script.

Функция $modx->sanitizeString()

Еще есть одна убойная функция $modx->sanitizeString(). Она не только удаляет HTML и PHP теги, но удаляет почти все символы кроме разрешенных. Как следует из названия, её применяют к строкам (ведь цифровые данные мы фильтруем принудительным приведением к соответствующему типу).

public function sanitizeString($str,$chars = array('/',"'",'"','(',')',';','>','<'),$allowedTags = '') {
    $str = str_replace($chars,'',strip_tags($str,$allowedTags));
    return preg_replace("/[^A-Za-z0-9_\-\.\/\\p{L}[\p{L} _.-]/u",'',$str);
}

По-моему, эта функция несколько не доработана, так как второй аргумент, в котором указываются символы для удаления, вообще ни на что не влияет. Как собственно и третий. В функции preg_replace() указан свой набор разрешенных символов, который не зависит ни от аргумента $char, ни от $allowedTags. Так что, сохранить скобки при обработке телефона не получится, даже если в аргументе $char ничего не указать. Как и разрешенный HTML тег. Вот что я имел под словом «убойная».

Несмотря на то, что переменные $_COOKIE и $_SERVER не передаются ни через форму, ни через параметры запроса, их использование без обработки может быть небезопасным. Поэтому их также нужно фильтровать вышеописанными способами.

Функции PHP

Ну и не забываем про встроенные возможности фильтрации самого PHP как для работы с данными запроса так и с любыми другими.

Защита от SQL-инъекций

Для защиты от этого вида угроз нужно использовать функцию mysql_real_escape_string() для экранирования специальных символов. Эта функция делает гораздо больше, чем устаревшие addslashes() и mysql_escape_string(). Во-первых, она облегчает ведение и чтение логов mysql, заменяя, например, символ перевода строки на "\n" и некоторые другие символы на escape-последовательности. Во-вторых, и самое главное — она корректно работает с многобайтными кодировками, принимая во внимание текущую кодировку MySQL и не портит, таким образом, тексты в кодировке Unicode.

С версии PHP 5.5.0 эта функция помечена как устаревшая и будет удалена в будущем. В качестве альтернативы используйте mysqli_real_escape_string() или PDO::quote().
// В $_GET['name'] лежит значение д'Артаньян (это самый безобидный пример)
// Экранируем
$name=mysql_real_escape_string($_GET['name']);
// Строим запрос, который будет работать без ошибок
$query="SELECT * FROM table WHERE name LIKE '$name'";

В MODX эту функцию заменяет $modx->quote(). Она добавляет кавычки (если нужно) и экранирует специальные символы. Т.е. тот же самый код можно переписать так

// Экранируем
$name=$modx->quote($_GET['name']);
// Переменную $name уже не надо оборачивать в кавычки
$query="SELECT * FROM table WHERE name LIKE $name";

Еще один способ обезопасить себя от подобной проблемы — подготавливать запрос к базе данных. И использовать для этого возможности PDO. В MODX есть удобное расширение для работы с PDO — xPDO. Я считаю данный способ лучшим вариантом при разработке в MODX. Вот некоторые примеры его использования — Про xPDO, Пара фокусов с xPDO, ряд статей по xPDO.

Заключение

Тема серьезная и требует внимательного изучения, если вы хотите повысить безопасность сайта.

1   11242

Комментарии ()

  1. cimazov 23 августа 2017 # 0
    Доброго дня. Подскажите, при использовании XPDO, есть ли смысл в дополнительном экранировании?
    К примеру
    $raz= $_GET['raz'];
    $query = $modx->newObject('Object');
    $query->set('sender', $raz);
    $query->save();
    
    Благодарствую
    1. Сергей Шлоков 23 августа 2017 # +1
      В данном случае может и не нужно. Но лучше взять за привычку обрабатывать пользовательские данные. Так например, год назад в Tickets была дыра, позволяющая злоумышленнику использовать sql injection. И xPDO никак не помог.
      // Так делать нельзя!!!
      $object = $modx->getObject('Object', $_GET['id']); 
      // Как минимум так
      $object = $modx->getObject('Object', (int) $_GET['id']);
      
      1. cimazov 23 августа 2017 # 0
        Весьма признателен. Спасибо
        p.s. пару дней назад пытался оставить здесь данный вопрос с другого аккаунта (skarb), однако всплывала ошибка: недостаточно прав
        1. Сергей Шлоков 23 августа 2017 # 0
          Видимо при регистрации произошел сбой, так как пользователь не попал в группу. Добавил вручную.
    2. Discover 13 февраля 2022 # 0
      Интересная статья!

      Функция $modx->sanitize()
      Ещё MODX позволяет отфильтровать сразу массив.
      Запускаю в консоли:
      <?php
      $arr = ['a<script>' => '</script>b'];
      $arr = $modx->sanitize($arr, $modx->sanitizePatterns);
      var_dump($arr);
      
      По идее, должен получить:
      array(1) {
        ["a"]=>
        string(1) "b"
      }
      
      но получаю:
      array(1) {
        ["a<script>"]=>
        string(10) "</script>b"
      }
      
      Пожалуйста, подскажите почему так? Ибо, глядя на такой результат, не понятен смысл.
      1. Сергей Шлоков 15 февраля 2022 # +1
        Что это за странная конструкция — часть тега в ключе, часть в значении? Фильтруется только значение.

        Это абсолютно нерабочий пример. Такой даже фильтровать не нужно — тег скрипт не опасен, так как он не весь попал.
        1. Discover 15 февраля 2022 # 0
          Спасибо! Уже успел разобраться, заглянув в код метода. Чистит только значение элементов массива.
          Что касается очистки тегов script, вырезание происходит, если в элементе есть открывающий и закрывающий тег script и именно в таком порядке. Что, в принципе, и логично.
          Из этого всего можно вынести хорошую мораль.
          Не надо спешить задавать вопрос.
          А если тебе в спешке задали вопрос, не надо на него спешно отвечать. И тогда задавший начнет шевелиться, а вопрос уже может стать ответом.

      Вы должны авторизоваться, чтобы оставлять комментарии.

      Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.