Как нужно проверять входящие данные

Фильтрация данных, поступающих со стороны пользователя, с точки зрения безопасности самый важный компонент любого веб-приложения. Сегодня я хотел бы рассказать какие основные составляющие по-моему мнению должна включать правильная обработка входящих данных. Первое и самое главное правило – все, что приходит от пользователя или каким-либо образом может быть им изменено, должно проходить обязательную проверку на стороне сервера. Однако всеми излюбленный метод, при котором ко всем элементам массивов GET, POST и Cookie применяется addslashes(), не является абсолютно безопасным. Пример такого кода:

<?php
if(!get_magic_quotes_gpc())
{
  $_GET = array_map('addslashes', $_GET); 
  $_POST = array_map('addslashes', $_POST); 
  $_COOKIE = array_map('addslashes', $_COOKIE);
}
?>

Дело в том, что, если в каком-либо запросе присутствует переменная, которую мы можем изменить, и она взята без кавычек, то даже экранирование кавычек не поможет:

<?php
include('connect.php'); //производит соединение с БД и содержит код выше
$blah=$_GET['blah'];
mysql_query("SELECT * FROM foo WHERE bar=$blah LIMIT 1");
?>

Не смотря на включенный magic_quotes_gpc, такой запрос подвержен SQL-инъекции, так как никаких лишних кавычек нам закрывать не надо, а при реализации инъекции нам кавычки вообще не нужны. Таким образом, все значения, которые участвуют в условиях после WHERE, необходимо обрамлять кавычками.

Хорошим тоном безопасного кода является предварительное инициализирование переменных, в которые будут записаны данные, поступающие со стороны пользователя. Некоторые разработчики, не задумываясь, используют функции extract() и import_request_variables() для помещения элементов и их значений из $_GET, $_POST и $_COOKIE в суперглобальный массив $GLOBALS, что ведет к произвольному инициализированию и перезаписи ранее инициализированных переменных. Забудьте про $GLOBALS и опцию register_globals! В PHP6 этого уже не будет, и переменных “без ресурса” мы больше не увидим, так как это пережитки эпохи третьего PHP. Каждый скрипт должен знать какие именно входящие переменные к нему поступают и откуда они поступают.

Весь функционал, ответственный за импортирование переменных из массивов GPC и обработку их значений, должен быть реализован в одной конкретной функции. Назовем ее к примеру import_var(). Первый аргумент данной функции – это имя переменной (одно или несколько). Он должен представлять собой массив, так как у разработчика должна быть возможность инициализировать сразу несколько однотипных переменных. Второй аргумент – это имя массива, откуда будет импортирована эта переменная. Можно использовать первые буквы – G, P и C. Третий – это тип данной переменной. В зависимости от поля применения переменных, типов может быть много:

  • INT числовое значение
  • NUM числовое значение, включающее числа с плавающей запятой
  • TEXT строковое значение
  • ALPHA строковое значение, допускающее только буквенные и числовые символы
  • MAIL строковое значение, представляющее собой email адрес
  • ARR массив
  • BOOL булева

Для каждого типа переменной должен быть предусмотрен отдельный способ фильтрации. Например для NUM – это проверка с помощью функции is_numeric, для INT – is_numeric и is_float, для MAIL – проверка с помощью регулярного выражения и т.д.

Четвертый аргумент – это максимальная длина значения. Отдельный аргумент необходим для этой цели, так как строковые значения могут иметь разную длину. Таким образом, прототип нашей функции будет иметь следующий вид:

import_var((array)$name,(string)$source,(string)$type,(int)maxlen=0)

Пример подобной функции я встретил в Seditio CMS, однако до версии 121 она была реализована не самым лучшим образом, так как некорректно обрабатывала массивы, что привело к обнаружению мной уязвимости в данном движке.

Join the Conversation

14 Comments

  1. > (string)$source
    – не думаю что хорошая идея. Фильтрации не происходит. Лучше использовать htmlspecialchars($source), например, при переменных которые скрипт получает через $_POST (сообщения, etc).
    > (array)$name
    Тоже не совсем хорошо. В некоторых случаях можно привести строковое значение в массив, например:

    изначально – script.php?test=value
    присваиваем тип array – script.php?test[]=value

    Часто вызывает ошибку, позволяя раскрывать реальные пути, что нередко помогает при взломе.
    Мое имхо, использовать регэкспы для этих целей (за исключением is_numeric, (num), тут все ясно)
    Для полного счастья не хватает практических примеров как можно реально избавиться от инъекций в значения переменных. В mysql часто помогает mysql_escape_string или mysql_real_escape_string. Как я уже упоминал, htmspecialchars() полезная функция.
    Ждем пост с примерами, думаю многим будет интересно 😉

  2. c0nst, может быть я не очень понятно написал, но полного кода функции, которую я предлагаю использовать в моем посте нет – это всего лишь прототип, т.е. аргументы и какие типы данных они должны представлять собой. Прототип я привел для примера, чтобы понять как вызывать данную функцию:

    <?php
    extract( //импортируем в текущую символьную таблицу данные из возвращаемого массива
    import_var( //вызов нашей функции для обработки переменных
    array('var1','var2','var3') ,//1ый аргумент - названия переменных
    'G',// из массива $_GET
    'INT',//тип данных integer
    5 //макс длина 5
    )
    );
    ?>

    Как уже писал, пример такой функции можно посмотреть в Seditio CMS
    >Ждем пост с примерами, думаю многим будет интересно
    Хорошо, попробую написать свою )

  3. как сделать так, чтобы при неправильном введении параметра id редиректило на какую-то страницу?

  4. А что за функция такая import_var ? Не нашел ее описания нигде =((

    меня тоже интересует как проверить числовой ли параметр и сделать редирект если не числовой.

  5. Неосилил (( Тупо спрошу, подойдет ли она для того чтобы обработать числовую переменную $id и переменную например для определения имени странички news.php $pagename на содержание sql inj или XSS ?

Leave a comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.