Это статья пентестера 0xold, который 8 месяцев занимался поиском уязвимостей для bug bounty и решил поделиться своим опытом и наблюдениями. В статье рассказывается о нескольких уязвимостях, которые были обнаружены с помощью инъекции нулевого байта. В целях конфиденциальности все проверяемые сайты будут обозначаться в статье как company.com.

Что такое нулевой байт?

Нулевой байт, часто обозначаемый как \0, — это специальный символ с нулевым значением. В программировании он используется для обозначения конца строки или данных. Инъекция нулевого байта заключается в манипулировании этим символом для эксплуатации уязвимостей.

1. Уязвимость при сбросе пароля и Parsing Confusion

Я тестировал CDN-приложение и решил проверить функционал сброса пароля. Во время тестирования обнаружил очень интересный параметр под названием callbackUrl.

В попытке сбросить пароль я добавил /test в конец URL и проверил свою электронную почту. В письме я получил следующий URL: https://company.com/auth/reset‑password/test?code=blabla

Я тут же попробовал изменить имя домена в параметре callbackUrl на evil.com, но, к сожалению, получил статус‑код 400 Bad Request.

Я расстроился, но решил потратить некоторое время на эту задачу, поскольку возможность контролировать хотя бы часть URL возвратного вызова сброса пароля (callbackUrl) показалась мне потенциально серьёзной уязвимостью.

Вот некоторые из полезных нагрузок, которые пробовал:

http://evil.com@company.net/auth/reset-password
http://evil.com%20@company.net/auth/reset-password
http://evil.com\@company.net/auth/reset-password
http://evil.com?@company.net/auth/reset-password
http://evil.com#@company.net/auth/reset-password

Были и другие, но все они вернули статус‑код 400. Тогда у меня появилась идея попробовать инъекцию нулевого байта. Однако, поскольку это был JSON‑запрос, я не мог просто вставить обычный URL‑закодированный %00. Поэтому я провёл небольшое исследование, прочитал RFC для JSON и выяснил, что могу вставить Unicode‑символы, используя последовательность \u.

Строка, содержащая только один обратный слэш (\), может быть представлена как \u005C. В этом случае эквивалент нулевого байта в URL (%00) в Unicode — это \u0000.

Итак, я попробовал следующий payload: https://evil.com\u0000@company.net

И это сработало!

2. Path Traversal → XSS

В этом приложении я проводил фаззинг в поисках скрытых параметров с помощью Param Miner, и один из параметров, который сразу привлёк моё внимание, назывался templatename. Я отправил запрос с значением templatename=0xold и проверил ответ сервера.

Встретил ошибку 500 Internal Server Error, что указывает на проблему на стороне сервера в бэкенде. Моё первоначальное предположение заключалось в том, что сервер попытался запустить файл с именем «0xold», но эта попытка завершилась неудачей, что привело к ошибке 500 — это может свидетельствовать о потенциальной уязвимости Local File Inclusion (LFI). Перед тем как продолжить, я решил сначала узнать, на какой ОС работает сайт, прежде чем пытаться внедрить какой‑либо payload.

Для определения операционной системы я воспользовался nmap: nmap -A Company.com

Итак, теперь мы знаем, что сайт работает под управлением Windows, и можем продолжить внедрение payload для LFI, характерных для Windows. Я начал с внедрения таких payload, как:

login.asp
logout.asp
../login.asp
..\login.asp
….//login.asp
….//login.asp%00
….\\login.asp
../../windows\win.ini
..\..\windows\win.ini

…и сотен других. Я использовал список payload для LFI из репозитория Seclists (JSON‑Haddix), но ничего не сработало — все попытки приводили к ошибке 500 Internal Server Error.

Потом мне пришла в голову идея, что, возможно, разработчик использует какой‑то механизм белого списка и возвращает ошибку 500, если параметр templatename не находится в этом списке. Чтобы проверить свою теорию, я решил воспользоваться инструментом CeWL для генерации пользовательского словаря.

Что такое CeWL?

CeWL (Custom Word List generator) — это Ruby‑приложение, которое сканирует заданный URL на определённую глубину и возвращает список слов, которые затем могут быть использованы для подбора паролей с помощью инструментов, таких как John the Ripper. По желанию CeWL может также следовать внешним ссылкам.

Я выполнил следующую команду для генерации словаря: cewl ‘https://company.net’ > wordlist

Затем я использовал инструмент FFUF для проверки сгенерированного словаря:

ffuf -c -w wordlist -u ‘https://company.net/login.asp?templatename=FUZZ’ -mc all -fc 500

Один из значений вернул статус-код 200 OK! Это оказалось название самой компании.

Значение параметра отражалось в HTTP‑ответе, поэтому я решил попробовать внедрить XSS. Однако всё, что не совпадало с названием компании, по‑прежнему возвращало ошибку 500 Internal Server Error. Как же можно это эксплуатировать? Возможно, использовать последовательность../? Я внедрил следующее значение в параметр templatename и проверил ответ: company.com/0xoldSaysHi/../

Отлично, сработало! Теперь давайте попробуем внедрить XSS. Я внедрил простой payload, чтобы проверить, удастся ли выйти за пределы HTML-структуры: company/0xoldSaysHi”>/../

Я смог внедрить символ двойной кавычки ("), но символ > по какой-то причине не отражался в ответе. Тогда я решил попробовать внедрить нулевой байт (%00) после символа >, чтобы посмотреть, что произойдёт: company/0xoldSaysHi">%00/../

Сработало!! Давайте быстро вызовем всплывающее окно с помощью alert. Я внедрил следующий payload: company/0xoldSaysHi”%00><%00img+src=x+>/../

3.Обход внутреннего WAF с помощью инъекции нулевого байта

Я тестировал приложение на предмет SQL‑инъекций, и когда ввёл обычную одинарную кавычку (') в параметр error_category, сайт вернул ошибку базы данных, что указывало на его уязвимость к SQL‑инъекциям. Отлично! Я сохранил запрос и запустил sqlmap, чтобы он выполнил всю сложную работу за меня.

Сохранил запрос в файл с именем r и выполнил следующую команду: python3 sqlmap.py -r r --batch --dbs --random-agent

Спустя некоторое время после запуска sqlmap он вернул тайм-аут соединения. Сначала я подумал, что приложение не смогло справиться с количеством запросов, отправленных sqlmap, и сервер упал. Я выполнил простой запрос с помощью curl, но получил ошибку подключения. Попробовал тот же запрос с моего компьютера — работает. Хм, возможно, есть какой-то внутренний WAF? Я решил эксплуатировать уязвимость вручную через свой ПК.

Попробовал простой UNION-запрос 'UNION+SELECT+1337-- -

Сервер завис. Тогда я изменил запрос на UnION/**/sElect+1337

Сервер снова завис. Попробовал изменить значение на 'test+test+1337-- -

Вернулась ошибка базы данных. После небольшого количества проб и ошибок я понял, как работает их внутренний WAF. WAF анализировал HTTP‑запросы, и если находил вредоносный запрос, то зависал. Если я отправлял три запроса подряд, и сервер зависал, мой IP блокировался.

Я попробовал внедрить нулевой байт (%00) между каждым ключевым словом MySQL, чтобы запутать их WAF, и это сработало. После получения имён баз данных я внедрил следующий payload, чтобы получить все таблицы в базе данных admin:

f’+/**/UNION+%00seLecT+%00TABLE_NAME,TABLE_schema,NULL,NULL,NULL,NULL+FROM+%00INFORMATION_SCHEMA.tables+WHERE+table_schema=’AdminDB’%23

Вот и всё, эксперимент завершён.

Спасибо за внимание. Ваш Cloud4Y. Читайте нас здесь или в Telegram‑канале!