3. Обход запрета пробелов
Обход запрета пробелов: SQL без пробелов
Пробел в SQL — это стандартный разделитель команд. Для систем защиты (WAF) цепочка символов вроде OR 1=1 с пробелами является явным сигналом атаки. Однако хакеры знают десятки способов заменить пробел другими символами, которые база данных поймет так же хорошо, как и оригинал.
Когда команда безопасности разворачивает WAF (Web Application Firewall) или пишет собственный middleware-фильтр в Go, первый соблазн — заблокировать «подозрительные» шаблоны: OR 1=1, UNION SELECT, --, ;DROP. Регулярки сканируют каждый параметр запроса и при срабатывании возвращают 403. Проблема в том, что SQL-парсер базы данных гораздо умнее наивной regex-проверки: он умеет восстанавливать структуру запроса даже когда между токенами стоят не пробелы, а табуляции, переводы строк, комментарии или даже круглые скобки. Хакер изучил мануалы PostgreSQL и MySQL внимательнее, чем разработчик защиты, и знает все легальные альтернативы пробелу.
Эта асимметрия знаний — корень класса атак под названием «whitespace bypass» или «obfuscation». Аналогия из физического мира: представь сейф с замком, который реагирует только на слово «открыть» произнесённое чётким голосом. Если злоумышленник скажет «о-т-к-р-ы-т-ь» по слогам, шёпотом или через рупор — замок не сработает на голосовой триггер, но механизм всё равно поймёт команду и откроет дверцу. То же самое с базой данных: парсер прощает синтаксические вариации, фильтр их не учитывает, инъекция проходит.
Разберём, как сделать SQL-инъекцию «невидимой» для простых фильтров за счёт альтернативных разделителей, и почему ни одна из этих обфускаций не работает против Prepared Statements в Go.
1. Механика: "Магия комментариев"
Большинство диалектов SQL (PostgreSQL, MySQL, SQLite) воспринимают комментарии как валидные разделители вместо пробелов. Это сделано для удобства разработчиков: можно встроить пояснение прямо посреди многострочного запроса (SELECT /* отчёт за май */ * FROM orders WHERE ...), и парсер просто игнорирует содержимое между /* и */. Но та же фича превращается в оружие, когда WAF фильтрует по слову OR с пробелами вокруг. Пустой комментарий /**/ занимает место пробела, для парсера базы это эквивалент whitespace, а для regex-фильтра — последовательность из четырёх «безобидных» символов.
Стандартный SQL:
' OR 1=1 --
Вариант для обхода фильтров:
'/**/OR/**/1=1/**/--
WAF может увидеть в этом лишь одну длинную непонятную строку и пропустить её. Но ваша база данных и Go-приложение интерпретируют этот код как полноценное условие OR 1=1. Если в Go-хендлере вы делаете query := "SELECT * FROM users WHERE name='" + userInput + "'" и потом db.Query(query), обфусцированная нагрузка попадает в SQL-парсер MySQL/PostgreSQL уже после того, как WAF её пропустил — а парсер с радостью её исполнит.
Помимо /**/, в реальных payload встречаются конструкции /*!50000 OR*/ 1=1 (MySQL-специфичные комментарии с условием версии — выполняются только в MySQL ≥ 5.0.0), --%0a (комментарий до конца строки плюс перевод строки как разделитель), ;%00 (null-byte для обрыва строки в C-based парсерах). Каждый диалект СУБД добавляет свои хитрости, поэтому однострочная regex-защита всегда отстаёт от арсенала атакующего.
2. Альтернативные разделители
В зависимости от среды и типа СУБД, хакеры используют разные символы-заменители. Браузер и стандартная Go-библиотека net/http декодируют URL-encoding автоматически перед тем, как параметр попадёт в r.URL.Query().Get("name") — то есть %09 превратится в табуляцию ещё до того, как разработчик увидит входные данные. Если фильтр срабатывает уже после декодирования, он должен учитывать ВСЕ whitespace-символы Unicode (а их десятки), а не только пробел 0x20.
| Заменитель | URL/Hex код | Эффективность |
|---|---|---|
| Табуляция | %09 |
Высокая |
| Новая строка | %0a |
Высокая |
| Плюс | + (в URL) |
Часто используется |
| Скобки | SELECT(id)FROM(users) |
Работает в ряде СУБД |
3. Таблица работы: Атака vs Фильтр пробелов
| Полезная нагрузка | Блокируется ли strings.Contains(input, " ")? |
Итог в базе данных |
|---|---|---|
OR 1=1 |
🔴 Да (Заблокировано) | — |
OR/**/1=1 |
🟢 Нет (Пропущено) | OR 1=1 |
OR+1=1 |
🟢 Нет (Пропущено) | OR 1=1 |
4. Почему примитивная проверка в Go бесполезна
Если вы пытаетесь использовать strings.Split(input, " "), чтобы разобрать и «проверить» части запроса — вы оставляете дверь открытой. Хакеру достаточно прислать табуляцию (%09), и ваша функция Split вернет всю строку целиком, не обнаружив в ней «плохих» пробелов, а инъекция беспрепятственно попадет в базу.
Типичная ошибка: разработчик пишет if strings.Contains(input, "UNION") || strings.Contains(input, "OR 1=1") { return error } и считает, что закрыл вектор. Атакующий присылает UNI/**/ON SE/**/LECT или OR/**/1=1, фильтр не находит точных подстрок, запрос проходит. Даже более сложные regex с альтернативами (UNION\s+SELECT) ломаются, потому что \s в Go-regex покрывает только базовые whitespace-символы из стандарта POSIX. Атакующий присылает Unicode-пробел U+00A0 (non-breaking space) или U+2003 (em space) — SQL-парсер MySQL их корректно интерпретирует как разделители, а regex \s пропускает.
Реальные кейсы из практики: в 2017 году исследователи показали, что коммерческий WAF от одного из top-3 вендоров пропускал UNION+SELECT (плюс вместо пробела), потому что URL-decoder работал ПОСЛЕ regex-сканера. CVE-2019-5418 в Rails использовал ;\n для обхода фильтров на ;. Урок один: попытки бороться с SQLi на уровне строкового фильтра — это вечная гонка вооружений, в которой атакующий всегда на шаг впереди.
Пробел — это лишь один из множества способов разделить команды в SQL. Не полагайтесь на строковые фильтры. Только использование Prepared Statements гарантирует, что структура вашего запроса не будет изменена никакими символами-разделителями.
Архитектурный принцип: разделяй контрольные данные (структуру запроса) и пользовательские данные (значения параметров) на уровне протокола, а не на уровне строки. Prepared Statements реализуют это через bind-параметры: сначала драйвер отправляет в БД шаблон SELECT * FROM users WHERE name = ?, парсер строит план выполнения по этому шаблону, а потом отдельно передаются значения. Никакая обфускация в значении не может превратиться в новый SQL-токен — для парсера эти байты уже просто данные, не код. Это та же идея, что лежит в основе HTTP/2 vs HTTP/1.1: где-то нужно жёстко разделить header-frame и data-frame, иначе любая инъекция в данные ломает структуру.
Соответствие требованиям: OWASP Top 10 — A03:2021 Injection, #3 в рейтинге; CWE-89 «Improper Neutralization of Special Elements used in an SQL Command»; PCI-DSS 6.5.1 явно требует защиты от инъекций для систем, обрабатывающих карточные данные. Аудиторы PCI отвергнут любое решение, которое опирается только на WAF без параметризованных запросов на бэкенде.
Теперь перейдем к одной из самых актуальных тем современного веба — SQL-инъекциям внутри JSON-данных.
Продолжить чтение
Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу
Когда закончишь — отметь раздел, чтобы продолжить.