Перейти к содержимому
Назад к пути
Теория 4 мин чтения

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-данных.

Продолжить чтение

Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу

Когда закончишь — отметь раздел, чтобы продолжить.

🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru