MAATRIX / Блог / SQL-инъекция глазами логов: что видно в access.log

SQL-инъекция глазами логов: что видно в access.log

MAATRIX

Приложение падает или ведёт себя странно, вы открываете access.log — а там строки с одинарными кавычками, UNION SELECT и -- прямо в параметрах запроса. Первая реакция — паника: нас взломали. На практике в подавляющем большинстве случаев это автоматический скан, который прогоняют по вашему домену наравне с тысячей других. Но отличить безобидный шум от целевой атаки, которая реально что-то нашла, — отдельный навык, и именно access.log даёт для этого материал, если знать, куда смотреть.

Как выглядит попытка инъекции в сыром логе

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 FROMStacked 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*/, нестандартные функции конкретной СУБД) регулярно обходит готовые сигнатуры. Сигнатурный поиск ловит известное, а не гарантирует полноту.

Что делать с найденной подозрительной записью

  1. Соберите полную картину по IP и payloadgrep <IP> access.log за интересующий период, а не одна строка вне контекста.
  2. Проверьте код ответа. 200 с обычным размером тела — скорее всего, запрос ничего не сломал и параметризация отработала штатно. 500 или аномальный размер тела — повод посмотреть лог приложения на предмет ошибки СУБД.
  3. Найдите в коде хендлер этого параметра и убедитесь, что запрос к БД параметризован (prepared statement, bind-параметры, ORM), а не собран конкатенацией строк. Это единственная проверка, которая реально отвечает на вопрос «уязвимы или нет» — грепом логов на это не ответить.
  4. При признаках успешной целевой атаки (аномальные ответы, задержки при SLEEP-паттернах, рост исходящего трафика) — меняйте пароли и ключи, которые теоретически были в затронутой таблице, и рассматривайте случай как компрометацию, пока не доказано обратное.
  5. Забаньте источник через 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 ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →