Еще раз о правильной фильтрации

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

Недавно столкнулся с интересной SQL-инъекцией на одном популярном ресурсе, посвященному взлому и хакингу. С одной стороны, признаки успешно внедренного SQL-кода были явными, однако поведение уязвимого веб-приложения при особых запросах говорило о наличии некой фильтрации на определенные ключевые слова. Например запрос ‘ OR 1=1/* успешно проходил, но, подставив AND вместо OR, я постоянно получал пустую страницу – это, очевидно, следствие возникшей ошибки синтаксиса SQL-запроса. Проанализировав ответы скрипта, я убедился, что, во-первых, проверка на ключевые слова действительно имела место, и, во-вторых, производилась не просто проверка, а вырезание из строки запроса всех совпадений со списком ключевых слов. На PHP это выглядело бы примерно так:

<?php
$badwords = array("AND","UNION","SELECT","WHERE","INSERT","UPDATE","DELETE");
str_replace($badwords,"",$GET['id']);
?>

Обойти такую проверку сущий пустяк.

Все очень просто: нужно всего лишь вставить в фильтрумое слово еще какое-нибудь слово из списка. Таким образом, будут однократно вырезаны опасные слова, что сделает инжектируемые данные приемлемыми для синтаксиса конечного SQL-запроса. Пример:

-1 UNIandON SELEandCT username,password FRandOM users WHandERE id=1/*

В чем же заключается ошибка подобной фильтрации? Все дело в том, что PHP-сценарий должен производить лишь проверку со списком опасных слов, но не обработку путем вырезания. Пример грамотной реализации можно увидеть в Danneo CMS (/base/danneo.track.php):

<?php
$baddata = array("UNION",
                 "OUTFILE",
                 "FROM",
                 "SELECT",
                 "WHERE",
                 "SHUTDOWN",
                 "UPDATE",
                 "DELETE",
                 "CHANGE",
                 "MODIFY",
                 "RENAME",
                 "RELOAD",
                 "ALTER",
                 "GRANT",
                 "DROP",
                 "INSERT",
                 "CONCAT",
                 "cmd",
                 "exec",
                 "--",
                 // HTML LINE
                 "\([^>]*\"?[^)]*\)",
                 "<[^>]*body*\"?[^>]*>",
                 "<[^>]*script*\"?[^>]*>",
                 "<[^>]*object*\"?[^>]*>",
                 "<[^>]*iframe*\"?[^>]*>",
                 "<[^>]*img*\"?[^>]*>",
                 "<[^>]*frame*\"?[^>]*>",
                 "<[^>]*applet*\"?[^>]*>",
                 "<[^>]*meta*\"?[^>]*>",
                 "<[^>]*style*\"?[^>]*>",
                 "<[^>]*form*\"?[^>]*>",
                 "<[^>]*div*\"?[^>]*>");
foreach($baddata as $badkey => $badvalue){
if(is_string($inputdata) && eregi($badvalue,$inputdata)){ $badcount=1; }
}
/* ... */
if($badcount==1){
/* ... */
}
?>

Что касается того уязвимого сайта хакеров, то я его оставил в покое, тем более что он был грузинским, а войны я не хочу =)


21 comments:

  1. c0nst, 14. August 2008, 1:46

    ха) интересный обход проверки.
    За верный пример спасибо, сохраню для себя в виде функции. Еще пригодится =)

     
  2. Elekt, 31. August 2008, 17:12

    ->… пример грамотной реализации…

    не думаю.
    это скорее пример того, как разработчики в очередной раз доказали свою некомпетентность в вопросах безопасности, нежелание заказать и оплатить секуритианализ компетентному человеку\конторе.
    и расплачиваться за это распиздяйство как всегда будут клиенты.

    1) не фильтруются sleep,benchmark. мне будет более чем достаточного этого для атаки
    2) юзается ereg* уязвимый к нулл-байту: /index.php?bug=1’/*%00*/SQL-BYPASS

     
  3. Raz0r, 31. August 2008, 20:04

    спасибо за комментарий, действительно пример имеет недостатки в плане безопасности
    >не фильтруются sleep,benchmark. мне будет более чем достаточного этого для атаки
    без SELECT’а? Он ведь тоже фильтруется. Конечно, инъекция может быть в запросе к целевой таблице, например с пользователями, тогда можно обойтись и без SELECT’а (разумеется необходимо знать названия колонок этой таблицы).
    >юзается ereg* уязвимый к нулл-байту: /index.php?bug=1′/*%00*/SQL-BYPASS
    совершенно верно, однако это становится неактуальным, когда производится экранирование опасных символов

     
  4. .Slip, 7. November 2008, 0:11

    Вы меня конечно извините, но зачем писать 10-20-..-50 строчные функции для “фильтрации” ? Достаточно нескольких строк кода и стандартных функций пхп.

     
  5. Raz0r, 7. November 2008, 19:27

    @.Slip
    согласен, но дело в том, что эту пару строк кода придется выполнять всегда, когда нужно получить от пользователя данные. С этим связаны следующие проблемы:
    во-первых, при разработке, особенно это касается крупных проектов, могуть быть пропущены по невнимательности вызовы некоторых функций, скажем intval(). Имея одну универсальную функцию, вы никогда не допустите ошибок, так как без нее вообще не удастся получить данные.
    во-вторых, проверка с помощью нескольких строк кода не всегда эффективна, так как зачастую требуется выполнять множество операций, что включает анализ данных на предмет наличия недопустимых символов/слов/выражений, проверку длины данных и многое другое. Многие веб-разработчики используют даже не функции, а целые классы для обработки входящих данных. Чего стоит одна обработка html

     
  6. .Slip, 8. November 2008, 16:10

    >> Многие веб-разработчики используют даже не функции, а целые классы для обработки входящих данных. Чего стоит одна обработка html

    Ну да, в эти дебри я не лезу, парсеры там бб кодов и прочее, это дело для меня далёкое – я не девелопер. Я говорю именно про получение данных gpc и дальнейшую работу с ними в sql запросах.

    >> Имея одну универсальную функцию, вы никогда не допустите ошибок, так как без нее вообще не удастся получить данные.

    Ок, ну напишу я функцию в которую посылаются данные gpc, и содержимое функции будет примерно такой:

    $a = is_numeric($_REQUEST[‘a’]) ? $_REQUEST[‘a’] : mysql_escape_string($_REQUEST[‘a’]);
    return $a;
    // Писал скрипт поиска по таблицам именно таким способом

    Неужели не будет различия между этой функцией и 50 строчным извращением с preg_replace в какой нибудь цмс?

    И всё, что бы пользователь не вводил, он просто не способен вызвать ошибку мускуля. Я вообще не считаю себя кодером пхп, так что я могу ошибаться. Но тот же ввод пользователем символов % не способен вызвать ошибку при запросе … LIKE ‘%$a%’ … – всё работает на ура.

     
  7. Raz0r, 8. November 2008, 21:54

    Ок, ну напишу я функцию в которую посылаются данные gpc, и содержимое функции будет примерно такой:

    $a = is_numeric($_REQUEST[‘a’]) ? $_REQUEST[‘a’] : mysql_escape_string($_REQUEST[‘a’]);
    return $a;

    для самописного двига, исходник которого никто не увидит, действительно, вполне достаточно, однако когда речь идет о веб-приложении с открытым исходным кодом, код обработки входящих данных должен быть напорядок объемнее. Например, стоит добавить усечение данных до определенного количества символов, а также не рекомендуется получать данные из _REQUEST до выхода PHP 5.3, где будет новая директива request_order, т.е. необходимо точно знать из какого массива брать данные. Вообще, как я уже говорил, можно много чего добавить, но в некоторых случаях, действительно, разработчики, совершенно не разбираясь в тонкостях безопасного кода, добавляют много ненужного мусора, вот например:

    <?php
    // take out common malicious characters that are
    // typically NOT used on standard inputs
    $string = preg_replace( "/[$#;?]|(eval\()|(eval +\()|(char\()|(char +\()|(exec\()|(exec +\()/i", "", trim( rtrim( $string ) ) ) ;
    ?>
     
  8. Makaka, 10. November 2008, 10:33

    У меня вопрос про фильтрацию строки при регистрации пользователей
    Насколько необходимо следующее :

    if (ereg(“[^\x80-\xF7 [:alnum:]@_.-]”, $name)) return false;
    if (preg_match(‘/[\x{80}-\x{A0}’. // Non-printable ISO-8859-1 + NBSP
    ‘\x{AD}’. // Soft-hyphen
    ‘\x{2000}-\x{200F}’. // Various space characters
    ‘\x{2028}-\x{202F}’. // Bidirectional text overrides
    ‘\x{205F}-\x{206F}’. // Various text hinting characters
    ‘\x{FEFF}’. // Byte order mark
    ‘\x{FF01}-\x{FF60}’. // Full-width latin
    ‘\x{FFF9}-\x{FFFD}’. // Replacement characters
    ‘\x{0}]/u’, // NULL byte
    $name)) {
    return false;
    } // Copyright drupal =)

    Кроме null байта мне больше ничего непонятно

     
  9. Raz0r, 10. November 2008, 18:21

    полезность всех этих регулярок в некоторой степени присутствует, но авторы друпала, почему-то исходят из положения “что не запрещено, то разрешено”, хотя лучше следовать обратному принципу: “что не разрешено, то запрещено”, т.е. нужно задавать регулярку, которая допускает список только разрешенных символов, что существуенно сократит код.

     
  10. ekim, 31. January 2009, 23:26

    “PHP-сценарий должен производить лишь проверку со списком опасных слов”
    Вот из этого принципа все беды.
    В начале статьи был приведен пример обхода такой проверки.
    Потом написали еще больше опасных слов для проверки.
    Святые угодники! И снова нашлись опасные символы (sleep,benchmark)!

    Нужно не запрещать опасные символы, а делать “белый” список допустимых символов. А для работы с базой лучше всего использовать плэйсхолдеры (см. PDO, DBSiple). Подобные библиотеки не только устраняют инъекции, но и делают удобной работу с sql-запросами.

     
  11. Raz0r, 1. February 2009, 1:18

    Да, с примером грамотной реализации я здесь погорячился. Мысль насчет белого списка собственно я выразил коментом выше Вашего, но на момент написания поста еще этого принципа не придерживался =) Что касается плейсхолдеров полностью согласен.

     
  12. MONtrade, 18. March 2009, 10:52

    обход похож на пробив защиты от XSS, что-то вроде <script>alert();</script>

     
  13. wad, 13. July 2009, 13:11

    Столько сказано в нете про добавление ‘ к id полям…
    разве не простейший способ избавления от инъекций?
    @settype($var_id, “integer”);

     
  14. cawok, 12. November 2009, 0:13

    а у меня то в скриптике производится замена) короче попался мне дырявый скрипт) п.с. giganova

    так как я понял по кометах лучший выход это разрешить только нужные символы, а все остальное запретить? сорри я не сильно шарю в пхп.

    спасибо

     
  15. cawok, 12. November 2009, 0:52

    вот себе искали скриптик восстановление пароля forum.vingrad.ru/forum/topic-251826/anchor-entry2016835/0.html#entry2016835 вроде тут «что не разрешено, то запрещено»

     
  16. cawok, 13. November 2009, 3:11

    я ошибся

     
  17. GivioN, 4. April 2010, 11:15

    Реально ли совершить скуль инъекуцию если магик.ковычки в офф? Чтобы сделать скуль запрос нужна ковычка а её эта функция не дает сделать скуль запрос когда переменная $asd=1

    $country = $_POST[“country”];
    function tuc($mensaje)
    {
    if (ereg(“^[a-zA-Zа-яА-Я0-9\-_]{1,255}$”, $mensaje))
    {
    return $mensaje;
    }else{
    $asd=1;
    echo ” Введенные данные содержат запрещенные символы. Проверьте правильность ввода”;
    return $mensaje;
    }
    }
    $country = tuc($country);

     
  18. Raz0r, 5. April 2010, 16:46

    Да, вполне реально,так как функция ereg(i) не является бинарно безопасной, т.е. если передать нулл-байт, то функция будет проверять только то, что находится перед ним, а все остальные символы пропустит, возвратив true. Учитывая, что magic_quotes_gpc off, то никаких препятствий не имеется (по крайней мере из того кода, что приведен).

     
  19. GivioN, 5. April 2010, 20:44

    Спасибо, хороший твой блог)))
    http://secureblog.org/pub/secure_code.html

     
  20. Ctacok, 8. May 2010, 18:34

    Вот всегда удивлялся Веб-Программистам, данный метод я уже до этой статьи смог использовать.
    Наверное, самое главное для таких вещей, не preg_replace, а preg_match 🙂

     
  21. ddd, 28. May 2011, 14:17

    а почему бы вместо вырезания, str_ireplace не заменять искомую команду например на пробел?

    это же быстрее выйдет чем использование регулярок?

     

Write a comment: