
На CTF-турнире тиммейт убил 40 минут на web-таск, который решался одной кавычкой в параметре id — и следом UNION SELECT на три колонки. Он не SQLi не знал. Он пробовал payload'ы наугад, вместо того чтобы последовательно определить тип инъекции, количество колонок и структуру базы. SQL-инъекции на практике — не коллекция строк для копипаста, а методология: от первой кавычки в Burp Suite Repeater до момента, когда sqlmap вытягивает дамп за тебя. Ниже — полная цепочка эксплуатации SQL injection с акцентом на то, что реально работает в CTF и на пентесте.
Эксплуатация SQL-инъекций — не изолированный трюк. В терминологии MITRE ATT&CK она вписывается в конкретную последовательность: Подробнее — в нашем обзоре пентест веб-приложений.
CWE-89 (Improper Neutralization of Special Elements used in an SQL Command) — третье место в CWE Top 25 2024 года. SQL-инъекция стабильно входит в OWASP Top 10 как A03:2021 — Injection. Если приложение работает с SQL и принимает пользовательский ввод — проверять на SQLi нужно в первую очередь. Без вариантов.
Насколько SQLi актуальна в 2025 году — показывает CVE-2025-1094: уязвимость в PostgreSQL (CVSS 8.1 HIGH, CWE-149 — Improper Neutralization of Quoting Syntax) в функциях экранирования libpq — PQescapeLiteral(), PQescapeIdentifier() и других. EPSS-скор 0.9003 (top 1% — экстремально высокая вероятность эксплуатации). Затронуто несколько версий PostgreSQL. По данным CISA SSVC, статус эксплуатации — 'none', автоматизация — 'no'. Эксплуатация требует специфического паттерна: результат функций экранирования должен использоваться для построения ввода в psql (интерактивный терминал PostgreSQL). В этом сценарии ни один WAF не спасёт.
[Применимо: внешний/внутренний пентест, black box, веб-приложения с backend на MySQL / PostgreSQL / MSSQL / SQLite]
Поиск SQL-инъекций через Burp Suite начинается с маппинга поверхности атаки. Перехватываем трафик через Proxy, проходим приложение как обычный пользователь и фиксируем все точки ввода: GET/POST-параметры, cookie, заголовки (X-Forwarded-For, Referer, User-Agent), JSON-тела API-запросов.
Требования к окружению: - ОС: Linux/macOS/Windows - Burp Suite Community Edition (бесплатный) или Professional - Целевой стенд: DVWA, bWAPP, WebGoat или CTF-таск с веб-формой - 4 ГБ RAM минимум (8 ГБ рекомендуется для VM + Burp Suite) - Python 2.7/3.x для sqlmap
Согласно OWASP Web Security Testing Guide, тестировать нужно каждое поле отдельно — одна переменная меняется, остальные остаются константами. Меняешь несколько параметров одновременно — не поймёшь, какой именно уязвим. Звучит очевидно, но на CTF половина людей именно на этом и сыпется.
Первый тест — отправить одинарную кавычку ' в параметр через Burp Repeater и наблюдать за ответом. Сервер вернул You have an error in your SQL syntax (MySQL) или Unclosed quotation mark before the character string (MSSQL) — точка входа найдена. Ответ изменился без явной ошибки (пустая страница, другой HTTP-код) — возможна blind-инъекция.
Дальше — отправляем id=1' AND '1'='1 (ожидаем нормальный ответ) и id=1' AND '1'='2 (ожидаем пустой или изменённый ответ). Разница в поведении = boolean-based blind SQLi. Дополнительно проверяем id=1' AND SLEEP(5)-- — ответ задерживается ровно на 5 секунд, значит time-based blind подтверждён.
Тип инъекции определяет стратегию эксплуатации и затраченное время:
| Тип | Индикатор | Скорость извлечения данных | Типичное применение |
|---|---|---|---|
| Error-based | SQL-ошибка с данными в ответе | Высокая — данные в одном запросе | Legacy-приложения, verbose errors |
| UNION-based | Данные запроса отображаются на странице | Высокая — произвольные данные за запрос | CTF, приложения с отображением результатов |
| Boolean-based blind | Ответ различается для true/false | Средняя — один бит за запрос | Приложения без вывода ошибок |
| Time-based blind | Ответ одинаковый, различие только в задержке | Низкая — один символ за задержку | Максимально закрытые приложения |
| Out-of-band | Данные уходят через DNS/HTTP | Зависит от канала | Когда другие методы заблокированы |
На CTF чаще всего встречаются UNION-based и blind — организаторы любят варьировать сложность именно через ограничение обратной связи.
[Применимо: веб-приложения, где результат SELECT-запроса выводится на страницу; внешний/внутренний пентест, CTF]
UNION-based инъекция — рабочая лошадка и для CTF, и для реальных проектов. Суть: приложение выполняет SELECT и выводит результат. Мы дописываем UNION SELECT, чтобы приклеить к легитимному выводу данные из любой таблицы.
Прежде чем использовать UNION, нужно выяснить количество колонок в оригинальном запросе — несовпадение вызовет ошибку. Два способа:
ORDER BY — инкрементируем номер: ' ORDER BY 1--, ' ORDER BY 2--, ' ORDER BY 3--, пока сервер не вернёт ошибку. ORDER BY 4 ломается, ORDER BY 3 работает — колонок три.
UNION SELECT NULL — подставляем NULL-значения: ' UNION SELECT NULL--, ' UNION SELECT NULL,NULL--, ' UNION SELECT NULL,NULL,NULL--. NULL подходит к любому типу данных, поэтому ошибок типизации не будет. Когда количество NULL-значений совпадёт с колонками — запрос отработает чисто.
Работает если: приложение выводит результат SELECT на страницу; нет WAF, блокирующего UNION; тип СУБД поддерживает UNION (все основные). Не работает если: приложение не выводит результат запроса (нужна blind-техника); WAF фильтрует ключевое слово UNION (нужен обход через UNiOn или /*!50000UNION*/).
После определения количества колонок (допустим, три) — находим, какие из них отображаются на странице. Отправляем ' UNION SELECT 'a','b','c'-- и смотрим, где появятся буквы. Допустим, видны вторая и третья — через них и будем тянуть данные.
Цепочка для MySQL:
table_name из information_schema.tables, фильтруем по table_schema=database(). Payload: ' UNION SELECT NULL,table_name,NULL FROM information_schema.tables WHERE table_schema=database()--.column_name из information_schema.columns с фильтром WHERE table_name='users'.' UNION SELECT NULL,username,password FROM users--.В Burp Repeater каждый шаг — отдельный запрос с наблюдением за ответом. Весь процесс от кавычки до дампа таблицы users на типичном CTF-таске занимает 5-10 минут при работе по методике. Ковыряешься наугад — легко потратишь час и так ничего не вытащишь.
[Применимо: стенды без кастомных error-страниц, legacy-приложения на PHP с display_errors=On, внешний/внутренний пентест]
Error-based SQLi эксплуатирует ситуацию, когда сервер возвращает текст SQL-ошибки прямо в HTTP-ответе. Задача — сконструировать payload, который вызовет ошибку, содержащую нужные данные. По сути, заставляем СУБД «проболтаться».
Для MySQL классический приём — EXTRACTVALUE() или UPDATEXML(): подставляем подзапрос, результат которого попадает в текст ошибки. Payload вида ' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()), 0x7e))-- вернёт ошибку с версией СУБД.
Для MSSQL работает приведение типов: ' AND 1=CONVERT(int, @@version)--. СУБД попытается привести строку версии к integer, не сможет — и выплюнет полную строку в тексте ошибки. Этот же приём, согласно OWASP WSTG, помогает определить тип и версию СУБД (fingerprinting) на этапе разведки.
Альтернативный вектор для MySQL — GROUP BY с RAND(): конструкция вида AND (SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2)x GROUP BY CONCAT((SELECT database()), FLOOR(RAND(0)*2))) генерирует ошибку Duplicate entry с именем базы данных в тексте. Некрасиво, зато работает.
Когда техника НЕ работает:
- Приложение использует кастомные error-страницы (HTTP 500 без деталей)
- WAF фильтрует функции EXTRACTVALUE, UPDATEXML, CONVERT
- Настроен display_errors=Off в PHP или аналогичный параметр
- СУБД сконфигурирована без verbose-ошибок
Если сервер не показывает ни данных, ни ошибок, но ответ различается для true/false условий — это boolean-based blind. Типичная ситуация: страница товара возвращает контент при id=1 AND 1=1 и пустую страницу при id=1 AND 1=2.
Алгоритм извлечения: посимвольно проверяем каждый символ целевого значения через SUBSTRING и бинарный поиск по ASCII-коду. Запрос ' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a'-- проверяет первый символ пароля. Перебор 95 печатных ASCII-символов через бинарный поиск — максимум 7 запросов на символ.
На CTF пароли часто хранятся как MD5-хеш (32 символа). Полный дамп одного хеша — порядка 224 запросов. Вручную через Burp Intruder это решается за пару минут с правильной настройкой payload-листа: позиция символа в первом payload set, набор символов 0-9a-f во втором. Муторно, но терпимо.
[Применимо: веб-приложения с идентичным ответом на true/false, black box, когда boolean-based не даёт разницы]
Time-based blind — крайний случай. Ответ сервера всегда одинаковый, единственный канал обратной связи — задержка. Для MySQL: ' AND IF(SUBSTRING(database(),1,1)='a', SLEEP(5), 0)--. Если первый символ имени базы — a, ответ придёт с задержкой в 5 секунд.
Для MSSQL аналог — WAITFOR DELAY: '; IF (SELECT SUBSTRING(db_name(),1,1))='a' WAITFOR DELAY '0:0:5'--.
Работает если: СУБД поддерживает функции задержки; нет жёсткого timeout на стороне reverse proxy. Не работает если: WAF детектирует SLEEP/WAITFOR по сигнатуре; rate limiting ломает timing; сетевой jitter превышает задержку payload'а.
Дамп 32-символьного хеша при 5-секундном sleep — минимум 160 секунд чистого времени. Почти три минуты на один хеш, и это в идеальных условиях. В CTF time-based обычно означает, что организаторы ожидают переход на автоматизацию — ручной перебор здесь нерационален.
sqlmap — стандартный инструмент для автоматизации эксплуатации SQL-инъекций. Поддерживает MySQL, PostgreSQL, Oracle, MSSQL, SQLite и ещё более 20 СУБД. Детектирует все основные типы: boolean-based blind, time-based blind, error-based, UNION query-based, stacked queries и out-of-band.
Требования к окружению:
- Python 2.7 или 3.x
- Установка: git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git
- Работает на любой ОС без дополнительных Python-пакетов для базовых функций (некоторые продвинутые фичи вроде --os-pwn требуют Metasploit)
- Никаких sudo/root — запускается от обычного пользователя
Типичный workflow от обнаружения до дампа:
# Проверка параметра на инъекцию + перечисление баз
sqlmap -u "http://target.com/page?id=1" --dbs --batch
# Сканирование из сохранённого Burp-запроса (максимальная глубина)
sqlmap -r request.txt --level=5 --risk=3 --batch
# Извлечение таблиц конкретной базы
sqlmap -u "http://target.com/page?id=1" -D dbname --tables
# Дамп таблицы users
sqlmap -u "http://target.com/page?id=1" -D dbname -T users --dump
--level=5 заставляет sqlmap тестировать cookie, User-Agent, Referer и другие заголовки — по умолчанию проверяются только GET/POST-параметры. --risk=3 включает OR-based injection, который на продакшене может затронуть данные через UPDATE/DELETE — на CTF безопасно, на боевом сервере думай дважды. --batch отвечает на все вопросы автоматически — критично для CTF, где времени в обрез.
Для API с JSON-телом: передаём запрос через файл (-r request.txt) — проще, чем конструировать командную строку с --data и --headers. В Burp Suite правый клик на запросе → Copy to file → скармливаем sqlmap.
Если тип СУБД уже известен (из fingerprinting'а в Burp), указываем --dbms=mysql — sqlmap пропустит фазу определения и перейдёт сразу к эксплуатации. Заметно ускоряет работу.
--technique=BEU ограничивает sqlmap только boolean, error и union техниками — полезно, когда нужно избежать time-based задержек. Полный набор: BEUSTQ (Boolean, Error, Union, Stacked, Time, Query).
sqlmap поставляется с более чем 200 tamper-скриптами для обхода WAF и фильтров. Основные:
space2comment — заменяет пробелы на /**/. Обходит фильтры, блокирующие пробелы.between — заменяет > на NOT BETWEEN 0 AND. Обходит фильтры на символ >.randomcase — рандомизирует регистр ключевых слов (SeLeCt). Обходит case-sensitive фильтры.charencode — кодирует payload в URL-encoding.space2mssqlhash — заменяет пробелы на %23%0A (MSSQL-специфичный).# Комбинация tamper-скриптов для обхода базового WAF
sqlmap -u "http://target.com/?id=1" \
--tamper=space2comment,between,randomcase \
--dbms=mysql --threads=10 --batch
На CTF WAF-bypass обычно решается одним-двумя tamper-скриптами. На реальном пентесте может потребоваться кастомный скрипт: sqlmap поддерживает пользовательские tamper'ы как Python-модули с функцией tamper(payload, **kwargs). Писать их несложно — по сути, простая подмена строк.
sqlmap нужно «навести» на конкретную точку инъекции — он не ползает по приложению и не обнаруживает attack surface самостоятельно. И вот тут начинаются грабли:
Нет краулинга attack surface. sqlmap тестирует переданный URL/запрос, но не находит новые эндпоинты. Для обнаружения нужен Burp Suite Spider или ручной обход. --crawl=3 существует, но работает примитивно — не сравнится с полноценным краулером.
Multi-step workflows. Инъекция достижима только после авторизации → заполнения формы → подтверждения действия? sqlmap не воспроизведёт цепочку автоматически. Придётся вручную: сохранить полный запрос с валидной сессией в файл, передать cookie через --cookie.
Second-order injection. Ввод сохраняется в базу и исполняется позже в другом контексте (например, имя пользователя при регистрации срабатывает в админской панели). sqlmap не связывает точку ввода с точкой срабатывания — это работа исключительно ручного анализа.
Бизнес-логика. sqlmap не поймёт, что amount=-1 в платёжной форме или quantity=0.1 в корзине могут привести к SQLi через нестандартную обработку на бэкенде.
Агрессивный WAF. Против WAF с ML-детекцией (Cloudflare, AWS WAF managed rules, ModSecurity CRS 4.x на параноидальном уровне) автоматический сканер может быть заблокирован по поведенческому профилю раньше, чем найдёт инъекцию. Tamper-скрипты не спасут, если WAF блокирует по частоте запросов, а не по сигнатурам.
Выбор подхода зависит от контекста. Алгоритм, который работает и на CTF, и на пентесте:
', '', 1 AND 1=1, 1 AND 1=2. Наблюдаем за разницей.--dbs → --tables → --dump. При наличии привилегий: --os-shell (MSSQL через xp_cmdshell), --file-read (MySQL с FILE-привилегией).| Ситуация | Подход | Обоснование |
|---|---|---|
| Видимые SQL-ошибки | Ручной (Burp Repeater) | 2-3 запроса до результата |
| UNION на простом GET-параметре | Ручной для колонок, sqlmap для дампа | Определить колонки быстрее вручную |
| Boolean-based blind | sqlmap | Ручной перебор нерационален |
| Time-based blind | sqlmap с --delay |
Ручной перебор невозможен в разумные сроки |
| WAF блокирует автосканер | Ручной подбор payload, затем sqlmap с tamper | Сначала понять, что блокируется |
| Second-order SQLi | Только ручной | sqlmap не поддерживает |
| POST с JSON body | sqlmap через -r request.txt |
Ручной для валидации, sqlmap для exploitation |
| Известна СУБД | sqlmap с --dbms=mysql |
Пропуск fingerprinting, заметное ускорение |
Этот подход — прямое применение методологии OWASP WSTG: ручное тестирование для обнаружения и классификации, автоматизация для эксплуатации и объёмных задач.
Большинство руководств по SQL-инъекциям останавливаются на двух полюсах: либо «вот sqlmap — нажми enter», либо «используйте prepared statements». Реальность сложнее. За последние пару лет я наблюдаю сдвиг: классические SQLi в GET-параметрах встречаются реже, зато растёт количество инъекций в JSON API, GraphQL-запросах и нестандартных заголовках — там, где разработчики не ожидают атаки и не фильтруют ввод. CVE-2025-1094 в PostgreSQL — характерный пример: уязвимость не в приложении, а в библиотеке экранирования самой СУБД. Функции PQescapeLiteral() и PQescapeIdentifier(), которым доверяли тысячи проектов, оказались неспособны нейтрализовать определённые quoting-конструкции при передаче результата в psql. WAF здесь бесполезен, потому что payload проходит через «безопасные» функции.
На CTF-площадках и Bug Bounty программах всё чаще появляются задачи на second-order injection и OOB-эксфильтрацию через DNS — техники, которые sqlmap из коробки не покрывает. Тот, кто умеет только запускать sqlmap -u ... --dbs, быстро упирается в потолок. Навык переключения между ручным анализом в Burp и автоматизацией — не бонус, а базовое требование для любого, кто работает с веб-уязвимостями всерьёз. На WAPT эту цепочку — от ручного обнаружения до полной эксплуатации через sqlmap с tamper-скриптами — проходят в нескольких модулях с лабами, где сценарии ближе к реальным пентестам, чем к учебным стендам DVWA.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «web».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...