Перейти к содержимому
← Каталог php SQL Injection

SQL Injection — обход WAF blacklist

WAF с blacklist-фильтром блокирует SQL-ключевые слова, но обходится через mixed case.

hardphpPro
Задача
# SQL Injection — обход WAF blacklist ## Сценарий Перед вами PHP-приложение интернет-магазина SecureShop. Это нетипичная задача для SQLi: разработчик уже знает про класс проблемы и в поиске по каталогу добавил **дополнительный слой защиты** — самописный «WAF» на основе чёрного списка SQL-ключевых слов. Перед выполнением запроса параметр `q` проверяется на наличие `SELECT`, `UNION`, `DROP` и других опасных конструкций; если что-то из списка найдено, запрос отвергается с сообщением «Suspicious input blocked by WAF». Идея «защитить уязвимый код фильтром опасных слов» — типичная для разработчиков, начинающих знакомиться с SQLi. На практике она не работает: количество способов выразить ту же SQL-конструкцию в обход конкретного blacklist-фильтра практически бесконечно — другой регистр, разделители-комментарии внутри ключевого слова, hex/url-кодирование, эквивалентные операторы. Любая фильтрация по чёрному списку проигрывает: достаточно одной щели — и payload проходит. И **сама исходная уязвимость никуда не исчезает** — за фильтром по-прежнему стоит обработчик, формирующий SQL-запрос через строковую конкатенацию; «WAF» лишь создаёт ложное чувство защищённости. В таблице пользователей есть привилегированная учётная запись `admin` с балансом, достаточным для покупки товара «CTF Flag» в каталоге; флаг отображается на странице профиля владельца такого заказа после оформления покупки. Обычный пользователь `demo` с балансом 500 купить флаг не может — его задача обойти WAF и эксплуатировать SQLi так, чтобы получить заказ «CTF Flag», не имея денег. ## Класс уязвимости SQL Injection через UNION-based extraction в сочетании со stacked-query insertion. SQL — регистронезависимый язык: ключевое слово в `SeLeCt`, `sElEcT` или `select` выполняется так же, как и `SELECT`. Если blacklist-фильтр сравнивает по точному совпадению с UPPERCASE-вариантом, любая смена регистра пропускает payload. Помимо смены регистра, классические техники обхода blacklist-фильтров: - **Mixed case** — `uNiOn SeLeCt` вместо `UNION SELECT` - **Inline-комментарии** — `UN/**/ION SE/**/LECT` (SQLite принимает `/* */` внутри идентификаторов) - **Hex/URL-кодирование** — `%55NION` декодируется веб-сервером в `UNION` после фильтра - **Альтернативные операторы** — `||` вместо `OR`, `LIKE` вместо `=` Любая комбинация из вышеперечисленного делает атаку возможной, поскольку код за фильтром формирует SQL через конкатенацию. ## Уязвимый паттерн ```php // «WAF» — регистрозависимая проверка по списку UPPERCASE-ключевых слов: $sqlBlacklist = ['SELECT', 'UNION', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'EXEC']; foreach ($sqlBlacklist as $keyword) { if (strpos($input, $keyword) !== false) { return false; // запрос блокирован, страница покажет «Suspicious input blocked by WAF» } } // ... // За фильтром — обычная конкатенация значения q в текст SQL-запроса: $query = "SELECT id, name, description, price, image FROM products WHERE name LIKE '%" . $search . "%'"; $stmt = $db->query($query); ``` Здесь видны обе проблемы сразу. Первая — функция `strpos` сравнивает подстроку **с учётом регистра**, а SQL регистр игнорирует — значит варианты `uNiOn`, `union`, `UnIoN` проходят фильтр и при этом распознаются БД как ключевое слово `UNION`. Вторая — даже если бы фильтр был идеальным, за ним всё равно работает классическая конкатенация: значение `$search` склеивается с текстом SQL, и любой пропущенный фильтром payload автоматически получает SQL-инъекцию в чистом виде. ## Цели 1. Подтвердите наличие WAF: стандартный payload вида `' UNION SELECT 1,2,3,4,5 --` должен возвращать ошибку «Suspicious input blocked by WAF». 2. Найдите способ обхода фильтра. Внимательно посмотрите, **как именно** значения сравниваются с чёрным списком: учитывается ли регистр? Сравнивается ли подстрока или слово целиком? Любая обнаруженная щель открывает доступ к SQLi за фильтром. 3. Через UNION-инъекцию извлеките данные из таблицы `users` (имена пользователей, хеши паролей). 4. Используя более продвинутую технику (stacked queries), создайте для текущего пользователя `demo` запись в таблице `orders` с `product_name = 'CTF Flag'` — это сделает флаг видимым на странице профиля. 5. Заберите CTF-флаг со страницы `/profile`. ## Данные | Параметр | Значение | |----------|----------| | Обычный пользователь | `demo` / `demo` | | Точка входа | `GET /catalog?q=` | | Endpoint флага | `GET /profile` (только при наличии заказа с `product_name = 'CTF Flag'`) |
🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru