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

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

Недавно столкнулся с интересной 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){
/* ... */
}
?>

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


Posted

in

by

Comments

21 responses to “Еще раз о правильной фильтрации”

  1. c0nst Avatar

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

  2. Elekt Avatar

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

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

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

  3. Raz0r Avatar

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

  4. .Slip Avatar
    .Slip

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

  5. Raz0r Avatar

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

  6. .Slip Avatar
    .Slip

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

    Ок, ну напишу я функцию в которую посылаются данные 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 Avatar
    Makaka

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

    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 Avatar

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

  10. ekim Avatar

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

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

  11. Raz0r Avatar

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

  12. MONtrade Avatar
    MONtrade

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

  13. wad Avatar

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

  14. cawok Avatar
    cawok

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

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

    спасибо

  15. cawok Avatar
    cawok

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

  16. cawok Avatar
    cawok

    я ошибся

  17. GivioN Avatar

    Реально ли совершить скуль инъекуцию если магик.ковычки в офф? Чтобы сделать скуль запрос нужна ковычка а её эта функция не дает сделать скуль запрос когда переменная $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 Avatar

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

  19. GivioN Avatar

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

  20. Ctacok Avatar

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

  21. ddd Avatar
    ddd

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

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

Leave a Reply

Your email address will not be published. Required fields are marked *

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