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

Фильтрация данных, поступающих со стороны пользователя, с точки зрения безопасности самый важный компонент любого веб-приложения. Сегодня я хотел бы рассказать какие основные составляющие по-моему мнению должна включать правильная обработка входящих данных. Первое и самое главное правило – все, что приходит от пользователя или каким-либо образом может быть им изменено, должно проходить обязательную проверку на стороне сервера. Однако всеми излюбленный метод, при котором ко всем элементам массивов 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 она была реализована не самым лучшим образом, так как некорректно обрабатывала массивы, что привело к обнаружению мной уязвимости в данном движке.


14 comments:

  1. Viper, 14. May 2008, 21:04

    как с тобой связаться можно?

     
  2. Raz0r, 14. May 2008, 21:33

    стучи в ICQ 5o221o или пиши на email admin[at]raz0r.name

     
  3. c0nst, 16. May 2008, 2:02

    > (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() полезная функция.
    Ждем пост с примерами, думаю многим будет интересно 😉

     
  4. Raz0r, 16. May 2008, 13:06

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

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

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

     
  5.  

    […] дополнение к прошлому посту, и как подметил автор SecureBlog’а, хотел бы добавить […]

     
  6.  

    […] уже не раз излагал свои мысли по поводу принципов грамотной фильтрации […]

     
  7.  

    […] дополнение к прошлому посту, и как подметил автор SecureBlog’а, хотел бы добавить […]

     
  8.  

    […] уже не раз излагал свои мысли по поводу принципов грамотной фильтрации […]

     
  9. Я, 21. February 2009, 22:33

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

     
  10. Raz0r, 22. February 2009, 14:13
    <?php
    $id = import_var('id','GET','INT');
    if($id === false) {
    	header('Location: somepage.php');
    	exit();
    }
    ?>
     
  11. wOrker, 13. March 2009, 14:40

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

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

     
  12. Raz0r, 13. March 2009, 16:10
  13. wOrker, 13. March 2009, 17:58

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

     
  14. Raz0r, 14. March 2009, 14:23

    вполне

     

Write a comment: