SQL-инъекция глазами логов: что видно в access.log
Приложение падает или ведёт себя странно, вы открываете access.log — а там строки с одинарными кавычками, UNION SELECT и -- прямо в параметрах запроса. Первая реакция — паника: нас взломали. На практике в подавляющем большинстве случаев это автоматический скан, который прогоняют по вашему домену наравне с тысячей других. Но отличить безобидный шум от целевой атаки, которая реально что-то нашла, — отдельный навык, и именно access.log даёт для этого материал, если знать, куда смотреть.
Содержание
- Как выглядит попытка инъекции в сыром логе
- Таблица сигнатур: что означает каждый паттерн
- Автоматический скан или целевая атака
- Как искать паттерны в больших логах
- Когда grep не хватает: инструменты для системного мониторинга
- Что делать с найденной подозрительной записью
- Логи — это диагностика, а не защита
Как выглядит попытка инъекции в сыром логе
Access.log веб-сервера (nginx, Apache) обычно пишет то, что ушло в GET-параметрах и URI целиком — этого достаточно, чтобы увидеть большинство автоматических атак. POST-тело в стандартный access.log не попадает, туда нужен отдельный лог приложения или кастомный log_format, который логирует тело запроса.
Типичная строка с попыткой инъекции в GET-параметре:
203.0.113.45 - - [28/Aug/2026:14:22:07 +0000] "GET /product.php?id=1' OR '1'='1 HTTP/1.1" 200 3421 "-" "Mozilla/5.0"
203.0.113.45 - - [28/Aug/2026:14:22:09 +0000] "GET /product.php?id=1' UNION SELECT username,password FROM users-- HTTP/1.1" 500 182 "-" "Mozilla/5.0"
203.0.113.45 - - [28/Aug/2026:14:22:11 +0000] "GET /product.php?id=1' AND SLEEP(5)-- HTTP/1.1" 200 3421 "-" "Mozilla/5.0"
Что здесь видно сразу:
- Одинарные и двойные кавычки (
',") в значении параметра — самый частый и надёжный маркер. Легитимныйidтовара кавычек не содержит никогда. UNION SELECT— попытка объединить результат легитимного запроса с данными из другой таблицы, часто с перечислениемNULL-колонок, потому что атакующий подбирает их число.- Комментарии SQL —
--,#,/*...*/— «обрезают» остаток оригинального запроса после инъекции. OR '1'='1'/OR 1=1— превращает условиеWHEREв всегда истинное.SLEEP(),WAITFOR DELAY,pg_sleep()— маркеры слепой инъекции по времени: если ответ задерживается на заданное число секунд, запрос выполнился.information_schema,sysobjects— попытка вытащить структуру базы данных черезUNION SELECT table_name FROM information_schema.tables.- Коды ответа 500 там, где обычно 200 или 404 — если приложение не обрабатывает ошибку СУБД и отдаёт stack trace, это утечка информации для атакующего.
Отдельно стоит смотреть на URL-кодирование: часть сканеров сразу кодирует спецсимволы, чтобы не спугнуть примитивные фильтры — %27 вместо ', %2D%2D вместо --. Такие запросы не совпадут с шаблоном на кавычку при поиске «как есть» — нужно либо декодировать лог перед поиском, либо расширять регулярку под закодированные варианты.
Таблица сигнатур: что означает каждый паттерн
| Паттерн | Что это | Насколько тревожно |
|---|---|---|
', " | Проверка на экранирование ввода | Базовая разведка, очень частый шум |
OR 1=1, OR '1'='1' | Обход условия WHERE | Классика, обычно в связке с кавычкой |
UNION SELECT | Извлечение данных из другой таблицы | Серьёзно, особенно с рядом NULL-колонок |
--, #, /* | Комментирование остатка запроса | Почти всегда сопровождает атаку |
SLEEP(, WAITFOR DELAY, pg_sleep( | Time-based blind SQLi | Серьёзно, признак автоматизированного инструмента |
information_schema, sysobjects | Разведка структуры БД | Серьёзно, целевая разведка |
0x + hex-строка | Обход фильтрации кавычек | Признак sqlmap или аналога |
; DROP TABLE, ; DELETE FROM | Stacked queries | Серьёзно, но реально срабатывает редко |
Важная оговорка: сигнатура в логе означает, что запрос был отправлен, а не что он сработал. Параметризованный запрос просто передаст строку 1' OR '1'='1 как обычное значение параметра, ничего не найдёт и вернёт пустой результат или 404. Сигнатура — это факт попытки, не факт компрометации.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверАвтоматический скан или целевая атака
Это ключевой вопрос, потому что реакция разная: скан вы фиксируете и баните источник, целевую атаку — расследуете вручную.
Признаки скана: один и тот же набор из десятков типовых payload прогоняется по всем параметрам всех URL подряд — не только id, но и page, sort, search, там, где параметра логически быть не может; запросы идут плотно по времени, десятки в секунду с одного IP; User-Agent либо инструментальный (sqlmap, Nikto, python-requests), либо типовой браузерный, повторяющийся тысячи раз с разных IP; payload шаблонный — парные AND 1=1 / AND 1=2 (сравнение разницы в ответе) или сложные вложенные конструкции, которые вручную никто не печатает; в том же временном окне видны попытки других атак — path traversal, поиск .env, wp-config.php.bak — типичная картина неспециализированного сканера уязвимостей.
Признаки ручной атаки: payload сфокусирован на одном конкретном параметре, явно выбранном не случайно; между запросами паузы, похожие на человеческое мышление, а не миллисекунды; payload логично эволюционирует — сначала кавычка, потом ORDER BY N-- несколько раз подряд с разным N (подбор числа колонок), потом сам UNION SELECT с осмысленными именами колонок вроде username, password, email; атака продолжается после неудач с вариациями обхода фильтра — hex-кодирование, смена регистра (UnIoN SeLeCt), альтернативные комментарии; соседние запросы показывают, что атакующий изучил приложение заранее — обращается к неиндексируемым URL, использует правильные ID существующих записей.
На практике грань размыта: человек может найти уязвимый параметр вручную, а дальше натравить на него sqlmap -u "https://site/product.php?id=1" с конкретной точкой входа. В логе это выглядит как плотная серия шаблонных запросов, но сфокусированных на одном параметре — гибрид признаков из обоих списков и самый опасный случай: инструмент, направленный осознанно.
Как искать паттерны в больших логах
На активном сервере access.log за день может весить сотни мегабайт, и вычитывать его глазами бессмысленно. Базовый инструмент — grep -E с расширенными регулярками, желательно нацеленными на комбинации ключевых слов, а не на одну кавычку (кавычка сама по себе бывает в легитимных данных — например, в имени вида O'Brien):
grep -EiI "union([[:space:]]+all)?[[:space:]]+select|or[[:space:]]+1[[:space:]]*=[[:space:]]*1|sleep\([0-9]+\)|information_schema|--[[:space:]]" \
/var/log/nginx/access.log
Посчитать уникальные IP с подозрительной активностью за сутки — сигнал, скан это или целевая атака (много IP с одинаковым payload — ботнет, один IP с разнообразным payload — вероятно, целевая):
grep -EiI "union[[:space:]]+select|or 1=1|sleep\(" /var/log/nginx/access.log \
| awk '{print $1}' | sort | uniq -c | sort -rn | head -20
Посмотреть, по каким параметрам бьют чаще всего, чтобы понять, какой код проверять в первую очередь:
grep -EiI "union[[:space:]]+select" /var/log/nginx/access.log \
| awk -F'"' '{print $2}' | grep -oE '[a-zA-Z0-9_]+=' | sort | uniq -c | sort -rn
Логи, хранящиеся сжатыми по датам (access.log.2.gz), читаются zgrep вместо grep — то же самое, но без предварительной распаковки.
Отдельная проблема — URL-кодированные payload вроде %27%20OR%20%271%27%3D%271: обычный grep на ' их не найдёт. Практичный подход — расширить регулярку под закодированные варианты (%27, двойное кодирование %2527 тоже встречается) либо декодировать релевантные строки перед анализом, либо настроить nginx логировать уже нормализованный $request_uri.
Когда grep не хватает: инструменты для системного мониторинга
Разовый grep закрывает вопрос «что произошло вчера», но для постоянного мониторинга нужен слой выше:
- fail2ban с кастомным фильтром — доступный вариант для одного сервера: описываете regex на типовые SQLi-паттерны в
/etc/fail2ban/filter.d/, баните IP после N совпадений за период. Не остановит целевую медленную атаку, но эффективно отсекает автоматических ботов. Подробная настройка правил разобрана в статье про настройку правил fail2ban для nginx. - GoAccess — быстрый интерактивный анализатор access.log в реальном времени; не заточен под security-паттерны специально, но удобен, чтобы увидеть аномальные всплески запросов к конкретным URL или с конкретных IP, от которых уже отталкиваться grep'ом.
- Централизованный сбор логов (Graylog, Grafana Loki, ELK) — если серверов несколько или трафика много, искать паттерны в разрозненных файлах на каждой машине неудобно. Сборщик позволяет держать сохранённые поисковые запросы на типовые сигнатуры и получать уведомление при превышении порога, а не находить атаку через неделю при разборе инцидента.
- WAF (ModSecurity с OWASP CRS, Cloudflare) — блокирует часть паттернов до того, как запрос попадёт в приложение, и логирует срабатывания в отдельный, уже размеченный лог.
Ни один из этих инструментов не гарантирует, что нетиповая инъекция не пройдёт мимо — обфускация (двойное кодирование, комментарии вида /*!50000SELECT*/, нестандартные функции конкретной СУБД) регулярно обходит готовые сигнатуры. Сигнатурный поиск ловит известное, а не гарантирует полноту.
Что делать с найденной подозрительной записью
- Соберите полную картину по IP и payload —
grep <IP> access.logза интересующий период, а не одна строка вне контекста. - Проверьте код ответа. 200 с обычным размером тела — скорее всего, запрос ничего не сломал и параметризация отработала штатно. 500 или аномальный размер тела — повод посмотреть лог приложения на предмет ошибки СУБД.
- Найдите в коде хендлер этого параметра и убедитесь, что запрос к БД параметризован (prepared statement, bind-параметры, ORM), а не собран конкатенацией строк. Это единственная проверка, которая реально отвечает на вопрос «уязвимы или нет» — грепом логов на это не ответить.
- При признаках успешной целевой атаки (аномальные ответы, задержки при SLEEP-паттернах, рост исходящего трафика) — меняйте пароли и ключи, которые теоретически были в затронутой таблице, и рассматривайте случай как компрометацию, пока не доказано обратное.
- Забаньте источник через fail2ban или firewall, если это очевидный скан, и добавьте сигнатуру в постоянный мониторинг.
Общий подход к разбору инцидентов по логам, не только security-специфичный, описан в статье как читать логи и находить причину сбоя — та же последовательность (сначала временное окно, потом контекст, потом гипотеза) применима и здесь.
Логи — это диагностика, а не защита
Access.log ничего не защищает. Он пишется постфактум, когда запрос уже дошёл до приложения и, возможно, до базы данных. Даже мгновенный grep-алерт сработает уже после того, как первый — а возможно, и успешный — запрос отправлен и обработан. Логи — инструмент обнаружения и расследования, а не барьер.
Единственная настоящая защита — параметризованные запросы (prepared statements) или ORM, который их использует под капотом:
// Уязвимо: пользовательский ввод конкатенируется в SQL-строку
$result = $db->query("SELECT * FROM users WHERE id = '$id'");
// Защищено: значение передаётся отдельно от текста запроса
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
Во втором варианте база данных заранее знает структуру запроса и трактует $id строго как значение — даже если в нём окажется 1' OR '1'='1, это будет искаться буквально как строка id, а не изменит логику запроса. Именно поэтому большая часть находок в access.log — шум: современные фреймворки параметризуют запросы по умолчанию, и инъекция физически не срабатывает, сколько бы кавычек ни прислали.
WAF, fail2ban, мониторинг логов — это defense in depth, дополнительные слои, которые снижают шум, ловят автоматических сканеров и дают время на реакцию при целевой атаке. Но если в коде есть реальная уязвимость — конкатенация строк в SQL, самописное «экранирование» через str_replace, обходимое известными техниками — рано или поздно найдётся payload, который пройдёт мимо всех сигнатур, потому что они описывают известные атаки, а не закрывают саму уязвимость. Подробнее про то, что реально снижает риск на уровне сервера, а что нет, — в статье про защиту от SQL-инъекций на уровне сервера.
Относитесь к анализу access.log как к дымовой сигнализации, а не к огнетушителю. Она вовремя скажет, что где-то горит, но тушить — то есть чинить код — всё равно придётся отдельно.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если в логе много попыток SQL-инъекции, но сайт работает нормально, значит база уже взломана?
Не обязательно. Огромный объём таких строк — норма для любого публичного сайта: автоматические сканеры непрерывно прощупывают весь интернет по спискам доменов, независимо от того, есть ли у вас формы с параметрами. Наличие payload в логе говорит только о том, что запрос отправлен, а не что он выполнился.
Как понять, прошла ли инъекция мимо параметризованных запросов?
Надёжно — только аудитом кода: найти обработчик параметра и проверить, как формируется SQL-запрос. По логам косвенно можно судить по аномальным кодам ответа, необычному размеру тела ответа или задержкам, совпадающим с SLEEP-паттернами, но это лишь повод проверить код, а не замена проверке.
Нужно ли логировать тело POST-запросов, чтобы ловить инъекции в формах?
Да, если формы с пользовательским вводом — потенциальная точка входа (логин, поиск, комментарии), а стандартный access.log их не покажет. Это требует отдельной настройки и повышает чувствительность логов — стоит взвесить хранение таких данных с точки зрения требований по персональным данным.
Достаточно ли fail2ban, чтобы закрыть вопрос SQL-инъекций?
Нет. fail2ban банит IP по частоте совпадений с сигнатурами, что эффективно против автоматических сканеров, но бесполезно против одного медленного целевого запроса и не защищает от инъекции как таковой — при уязвимом коде единственный «правильный» запрос без явных сигнатур пройдёт мимо любого фильтра.
Стоит ли использовать WAF вместо разбора логов вручную?
WAF снимает основную массу рутинного шума и экономит время, но не отменяет периодический разбор логов — правила регулярно обходятся обфускацией, и часть трафика может идти мимо WAF напрямую к серверу.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →