Только для читателей Lifeexample возможно открыть интернет-магазин на Moguta.CMS со скидкой в 15%

<<< JavaScript поиск по странице || Корзина товаров и оформление заказа >>>

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

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

Здравствуйте уважаемые читатели блога LifeExample, рад вновь приступить к написанию полезных статей на тему php программирования, после двухнедельного перерыва. Сегодня я бы хотел рассказать о нужном в практике механизме защиты от SQL инъекций. Все сайты на сегодняшний момент интерактивны, и имеют формы ввода информации для взаимодействия с пользователями. Примерами являются: поля ввода логина и пароля для авторизации, поля для отправки текстовых сообщений через форму обратной связи, и др.

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

Что случится если передать серверу заведомо ложную информацию?

В лучшем случае, если в алгоритме работы php скрипта заложена соответствующая проверка, то информация будет отсеяна и пользователь уведомлен о не корректном вводе. Казалось бы, все хорошо, но пользователь может оказаться злоумышленником, и намеренно отправлять через поля ввода данные в виде кусочков SQL запросов. Такое действие получило название SQL инъекция.

Другими словами SQL инъекцией можно назвать намеренное действие пользователя направленное на внедрение любого SQL запроса в логику работы скрипта.

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

Пример SQL инъекции

В данном примере будет фигурировать:

  • Готовый скрипт аутентификации пользователя.
  • Готовая база данных.

Скачать архив ( Скачали: 900 чел. ) 

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

Наш php скрипт, подготовленный для тестирования взлома сайта, проверяет соответствие логина и пароля из формы с базой данных. Если пара логин и пароль верная, то выводит сообщение TRUE, в противном случае FALSE. Такую форму можно встретить на большинстве сайтов интернета, где вместо TRUE и FALSE пользователь будет получать какие либо права.
Код скрипта выглядит таким образом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?
    mysql_select_db('testwp', mysql_connect('localhost','root', '')) ; // <--настройки подключения к БД
    if($_GET["login"] && $_GET["pass"]){
        $login=$_GET["login"];
        $pass=$_GET["pass"];         
        $sql = "SELECT * FROM `user` WHERE `login`='$login' AND `password`='$pass'";
        $result = mysql_query($sql)  or die(mysql_error());
        if(mysql_num_rows($result)) echo "TRUE";
        else echo "FALSE";
    }
?>
<body>
    <form action="" method="get">
        Логин:<input type="text" name="login" value="<?=$_GET["login"]?>"/>
        Пароль:<input type="text" name="pass" value="<?=$_GET["pass"]?>"/>
        <input type="submit" value="submit"/>
    </form>
</body>

Введем в поля данные:

1
2
логин = admin
пароль = 123456

Если все настройки для подключения к БД верны, то в результате получим надпись TRUE, теперь введем не правильный пароль и увидим надпись FALSE. Все работает правильно — пары логин и пароль, проверяются, и корректно обрабатываются.

Теперь совершим маленькую SQL инъекцию (взлом). Введем любой логин и в поле пароля такую запись:

1
' OR 1='1

Удивительно, но это сработало, и скрипт теперь говорит нам, что авторизация прошла успешно и пара логин пароль введена, верно. Впечатляет, не правда ли? 🙂

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

Аналогично можно выполнить SQL инъекцию с помощью адресной строки, дописав к GET параметрам такую информацию:

1
http://[НАШ САЙТ]/index.php?login=admin&pass=luboj+'+OR+1='1

И в результате вновь увидим надпись TRUE.

Наш скрипт, получая данные, выполняет запрос к базе данных, следующего вида:

1
$sql = "SELECT * FROM `user` WHERE `login`='$login' AND `password`='$pass'";

При передаче в него переменных $login и $pass получится следующий запрос:

1
SELECT * FROM `user` WHERE `login`='admin' AND `password`='luboj '

И в соответствии с найденным или не найденным результатом мы увидим сообщение.

Что происходит при SQL инъекции

Передав в переменную пароля $pass строку:

1
' OR 1='1

На SQL сервер уйдет запрос такого вида:

1
SELECT * FROM `user` WHERE `login`='admin' AND `password`='' OR 1='1'

Т.е. к исходным параметрам проверки будет добавлено новое условие, OR 1=’1′ , выполняющееся всегда, так как единица при любом раскладе равна единице. Соответственно полученному запросу сервер вернет строки из таблицы пользователей, и скрипт выведет значение TRUE.

Пример SQL инъекции

Как защититься от SQL инъекций

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

Способов защиты от SQL инъекции много, но не один из них не дает 100% гарантии защищенности. Ведь на каждое действие есть противодействие, и особенно заинтересованный опытный хакер наверняка найдет способ, как получить доступ к вашей БД.

Но не будем отчаиваться, и всё-таки защитим нашу SQL базу от ненужных посягательств. Для этого прибегнем к использованию стандартных PHP функций mysql_real_escape_string() и sprintf().

Функция mysql_real_escape_string() экранирует специальные символы строки, таким образом, изменяя синтаксис параметров SQL запроса и приводя его в негодность.

Функция sprintf() возвращает отформатированную строку. Нам пригодятся директивы вида %s — строка , и %d – целое число.

Давайте примем за правило, что пароль в нашей системе должен быть всегда целым числом, и изменим код таким образом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?
    mysql_select_db('zalogticket', mysql_connect('localhost','root', '')) ;
    if($_GET["login"] && $_GET["pass"]){
        $login=$_GET["login"];
        $pass=$_GET["pass"];         
        $sql = "SELECT * FROM `user` WHERE `login`='%s' AND `password`='%d'";
        $query = sprintf($sql, $login, $pass);     
        $result = mysql_query($query)  or die(mysql_error());
        if(mysql_num_rows($result)) echo "TRUE";
        else echo "FALSE";

    }
?>
// далее HTML …

Обратите внимание, в $sql мы подставляем теперь не $login и $pass, как это было ранее, а директивы %s и %d. Этим мы учитываем условие, что пароль у нас должен быть числовым. Затем передаем это строку вместе с параметрами $login и $pass в функцию sprint() , для форматирования.

Давайте попробуем провести sql инъекцию, и вновь подставим в поле пароля строку:

1
' OR 1='1

Теперь такая атака проходит безрезультатно, скрипт возвращает надпись FALSE, так как функцией sprint() строка в переменной $pass заменяется на ноль и запрос принимает вид:

1
SELECT * FROM `user` WHERE `login`='admin' AND `password`='0'

Такая проверка лишь часть метода защиты от SQL инъекции , ведь в реальности пароль является не числом а строкой, и если вместо %d подставить директиву %s , то мы опять будем получать TRUE. Для того, чтобы избежать этого воспользуемся mysql_real_escape_string(), и код примет такой вид:

1
2
3
4
5
6
7
8
9
10
11
12
mysql_select_db('zalogticket', mysql_connect('localhost','root', '')) ;
if($_GET["login"] && $_GET["pass"]){
    $login=$_GET["login"];
    $pass=$_GET["pass"];         
    $sql = "SELECT * FROM `user` WHERE `login`='%s' AND `password`='%s'";
    $query = sprintf($sql,
            mysql_real_escape_string($login),
            mysql_real_escape_string($pass));
    echo $query."<br/>";
    $result = mysql_query($query)  or die(mysql_error());
    if(mysql_num_rows($result)) echo "TRUE";
    else echo "FALSE";

Переменные $login и $pass мы прогоняем через mysql_real_escape_string(), экранируя все спецсимволы, если таковые имеются.

В этом случае итоговый SQL запрос после инъекции будет иметь не корректный вид:

1
SELECT * FROM `user` WHERE `login`='a' AND `password`='iol \' OR 1=\'1'

И как следствие скрипт вернет в результате надпись FALSE.

Успешная защита от SQL инъекции

В данной статье о способах защиты от SQL инъекций, я показал только один метод, который, на мой взгляд, подойдет для базовой защиты любого php проекта, но в действительности их существует значительно больше. К таким методам можно отнести:

  • Создание менее привилегированного пользователя;
  • Отключение сообщений об ошибках;
  • Использование параметризованных запросов;
  • Использование хранимых процедур;
  • Использование функций блокировки;
  • Применение регулярных выражений;

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

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

Чтобы не пропустить публикацию следующей статьи подписывайтесь на рассылку по E-mail или RSS ленту блога.

Нравится

Комментарии

  • Web Designer

    Great, thanks for your share!
    @2012-04-13 11:21

  • ДЕМОН

    что за дебилизм? причем тут sprintf с %s ? от чего она будет защищать?

    • Mark

      sprintf — помогает отфильтровать числовые типы данных и строковые.

  • ДЕМОН

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

    • Владимир

      Если ты дебил, так не надо об этом кричать. Автор написал хорошую статью, показал один из вариантов защиты от SQL-инъекции. А ты по-сути кто такой? Что ты полезного сделал? Или только умеешь обсирать всех и вся? Не нравиться такой подход — вали с этого сайта.

  • Mark

    Для того чтобы, сразу отсеять неверные значения. А в целом можно и без нее, но с ней как-то по надежнее. Конечно mysql_real_escape_string — это мощная встроенная защита, если тебе уважаемый читатель не нравится подход со sprintf, ты можешь смело использовать только mysql_real_escape_string. Я тебе даже больше скажу, ты можешь и ее не использовать если в .htaccess прописать php_flag magic_quotes_gpc ON

  • ДЕМОН

    че там %s будет фильтровать??

  • Mark

    %s — ничего. %d — будет фильтровать цифры.

  • ДЕМОН
    1
    `login`='%s' AND `password`='%s'

    что-то я не вижу тут %d

  • Mark

    Где то, по тексту было такое. В конечном примере %в — не используется. В любом случае я не вижу тут криминала. Это всяко лучше чем:

    1
    `login`='$login' AND `password`='$password'
  • ДЕМОН

    по вашему вот это

    1
    `login`='%s' AND `password`='%s'

    лучше чем

    1
    `login`='$login' AND `password`='$password'

    ? сочувствую вам, ибо разницы абсолютно никакой.

  • Mark

    Разница, есть. Спасибо за сочувствие.

  • ДЕМОН

    вы идиот или притворяетесь? сами написали что %s ничего не фильтрует и что вернется TRUE, а сейчас пишете что есть разница. вы больной?

  • Mark

    Как долго я ждал, появления первого тролика на моем блоге! Уважаемый, вы с достоинством заслужили это звание. Поздравляю.)) По аплодируйте молодому тролю, господа.

    ДЕМОН Если вам все-таки интересно, почитайте мат часть, отностильно функции sprintf и ее назначения, а также о скорости конкатенации строк не явным методом.
    На этом я попрошу оставить эту тему, и не флудить в коментарии. Дальнейшие ваши, комментарии подобные предыдущему будут удалены.

  • mirashAK

    Автору спасибо, хороший пример простейшей инъекции, но пара проектов коллег на нём всё равно попалась 🙂 . Хотелось бы еще примеров формирования запросов или пользования вывода об ошибках.

  • Samigo

    Ваша защита 3 раза убрал то что я хотел сказать в другом посте. 🙂
    А можно ли видоизменит строку, а не убрать? Потому что,
    1вот так писать не все умеют. 🙂

    • По идее у меня не стоит никакой особой защиты. Только плагин Akismet. Не вижу, что бы смысл сообщений был потерян.

  • Samigo

    опять убрали 🙂
    Ладно другой вопрос:
    в чем разница:
    «mysql_real_escape_string()»
    и
    «mysql_escape_string()»

    Потому что, DW пишет такой код:
    «$theValue = function_exists(«mysql_real_escape_string»)
    ? mysql_real_escape_string($theValue)
    : mysql_escape_string($theValue);»

    • mysql_escape_string — Экранирует SQL спец-символы для mysql_query.
      mysql_real_escape_string — Экранирует специальные символы в строке, используемой в SQL-запросе, принмимая во внимание кодировку соединения.

  • Алексей

    Спасибо очень доходчиво!

  • vadim

    Здравствуйте Марк хотелось бы уточнить если переменная приходит от пользователя перед работай с БД к ней нужно применить mysql_escape_string или mysql_real_escape_string??? а если нужно вывести эту перменную на экран то htmlentities??? можно сразу к переменной применить htmlentities и mysql_escape_string

    • Все переменные приходящие от пользователей лучше пропускать через фильтры, которые бы экранировали спец символы, и заменяли опасные символы на безопасные. Такой подход защитит сайт от XSS атак и SQL инъекций. Другими словами использование htmlentities и mysql_escape_string вполне допустимо.

  • Алексей

    У меня вопрос, mysql_real_escape_string используется при sql запросах, а если например скачивание, у меня имя файла передается методом $_GET?

    • Для скачивания, ненадо экранировать кавычки.

  • Вадим

    Здравствуйте Марк ,а если использовать регулярки, допустим все данные проверять через них не используя встроенные функции??? хватит этого для безопасности???

    • Расскажите подробнее как вы это представляете? Вообще регулярками лучше не пользоваться без жесткой надобности, они очень требовательны к процессорному времени.

  • Вадим

    Вообще то я это вычитал из книги Фленова «php глазами хакера», ну вот допустим есть форма на ней логин и пароль вытаскиваем логин из глобального массива и пропускаем через регулярку, в регулярке разрешить только допустим цифры и буквы???? или лучше использовать встроенные функции для этого???я понимаю что они работают дольше ,но просто если это спасет от взлома может оно этого стоит???
    Просто в книге Фленов пишет что не стоит доверять встроенным функциям почему то???вот сейчас пишу класс валидации даных и думаю как лучше сделать

    • По моему вы описали несколько иную ситуацию не относящуюся к SQL инъекциям. Вы описали валидатор формы, т.е. введенный пароль должен соответствовать какому-то набору символов. В этом случае, действительно нужно использовать регулярные выражения.
      Sql инъекция подразумевает под собой передачу куска кода в поле формы, для защиты от нее, достаточно экранировать все кавычки. Для этого регулярки не нужны.

  • Андрей

    Огромное спасибо за труды, и ясность изложения, так как не везде можно так с примерами посмотреть и почитать! У меня еще вопрос по функции sprint(), если переменная состоит из чисел и букв, то в этом случае маркер будет %s или какой-либо другой?

  • Сергей

    Огромная благодарность автору за статью. Очень хорошо и понятно все написано. Приятно читать такой материал. Имея конкретные примеры из вашей статьи, сразу разобрался в данной теме. Спасибо!

  • Саша

    Здравствуйте. У меня вопрос: как можно запретить подмену переменной, скажем, id через исходный код в браузере Opera? В коде используется $_GET. Получается, что один пользователь создает сообщение, потом входит в его редактирование, далее открывает исходный код (все это в Opera), находит и меняет id своего сообщения на id другого пользователя и применяет изменения, затем редактирует свое сообщение в своем аккаунте (вводит что ему нужно) и нажимает на кнопку Сохранить. В результате изменяется сообщение другого пользователя. Может проблема тут: $editmes=$_GET[«admesedit»] ?

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

    • Милок

      Марк не понял вопроса, и ответ очевидно не верный. Саша, id отправителя нужно хранить в сессии, потому что отправить пользователь может что угодно.
      Принцып такой: Если кто-то отредактировал сообщение и нажал сохранить отправляется POST или GET запрос (лучше Пост) и уже на сервере проверяется соответствует ли id пользователя отправителя сообщения, id котой хранится в сессии данного пользователя. Тоеть, если я авторизуюсь на твоем сайте, и сайт откроет мне сессию, примерно так :
      session_start();
      $_SESSION[‘name’] = ‘Милок’;
      $_SESSION[‘id’] = ’15’ // оба значения берутся с БД, //после проверки логина и пароля
      и если я попытаюсь подписатся левым id у меня ничего не выйдет, потому что на сервере должна быть такая проверка:
      if($_POST[‘id’] == $_SESSION[‘id’]){
      //код..
      }else{
      // error допустим…
      }
      пусть и немного размыто я надеюсь ответил на твой вопрос.

  • Дмитрий

    хорошая статья, для начала понимания SQL инъекций.

  • AlexKerrey

    Вообще как уже раннее говорилось использовать

    1
    sprintf

    — очень плохая идея.
    Если вы подразумеваете что получаемые данные должны быть числом, тогда необходимо воспользоватся встроенными средствами фильтрации а не изобретать велосипед с квадратными колесами!
    ПРИМЕР:

    1
    2
    3
    4
    5
    6
    7
    8
    9
        function get_int($key) {
            return filter_input(INPUT_GET, $key, FILTER_VALIDATE_INT);
        }
       
        function set_def($str, $def) {
            return (is_null($str) || $str === false) ? $def : $str;
        }
       
        var_dump(set_def(get_int('test'), 1));
  • Антон

    А не легче ввести ограничения и запретить символы?

  • Боря

    Эти методы давно устарели, к тому же они не безопасны на 100%, уже давно используются готовые запросы к базе, плейсхолдеры, а ваш метод устарел, все равно спасибо за статью.

  • Vasja

    Мне пограмист делал спец символы какие то на этом сайте

  • Оставить комментарий

    Не отвечать

    Подписаться на комментарии к этой статье по RSS

    Яндекс.Метрика