Кратко
SSRF через вебхуки случается, когда по адресу, который вписал клиент, ходит сервер, стоящий внутри вашей сети. Проверка, которая это останавливает, работает по адресу, куда разрешилось имя, и должна пережить редирект, приходящий уже после её успеха. В списке OWASP за 2025 год отдельного пункта про SSRF нет: CWE-918 переехал внутрь A01 Broken Access Control, и это точнее называет суть, потому что запрос несёт позицию вашего сервера, а доступ даёт именно позиция.
Отправитель вебхуков ходит по адресу, который выбрал кто-то другой. Ничего сверх этого в подделке запроса со стороны сервера нет, и платёжная платформа делает это намеренно: обратный вызов и есть способ, которым система мерчанта узнаёт, что счёт оплачен.
Атакой такую возможность делает точка старта. Доставка идёт изнутри периметра, с хоста, у которого есть таблица маршрутов и собственный исходящий адрес, каких нет ни у одного браузера в публичном интернете. Кто-то регистрирует адрес, и платформа ходит туда за него, по расписанию, с повторами.
Держит одна проверка: по адресу, в который разрешилось имя, до открытия сокета — и она обязана пережить редирект, который пытается сдвинуть точку назначения уже после одобрения. Дальше о том, где эта проверка может стоять и чего её стойкость стоит мерчанту на другом конце.
Что такое SSRF через вебхуки?
Это класс ошибки, где точку назначения запроса, который делает ваш сервер, выбирает атакующий. Приходит она через единственное поле формы, построенное ровно для этого.
Привычные находки по SSRF — те, которых никто не собирался строить. Сборщик PDF идёт по ссылке на картинку; загрузчик аватара тянет её от имени пользователя. Никто не планировал раздавать посторонним HTTP-клиент, и постфактум находка читается как недосмотр. Отправитель вебхуков устроен наоборот: поле задокументировано, а у обработчика, который по нему ходит, другой работы нет.
OWASP переложил категорию в 2025 году. В списке 2021 года у SSRF был свой пункт A10; в списке 2025 года заголовка с таким именем нет, а CWE-918 стоит среди заметных слабостей внутри A01, Broken Access Control. Новое место описывает ущерб лучше прежнего имени. Запрос настоящий. Его отправила ваша инфраструктура, и то, до чего он дотянулся, достижимо именно из-за того, кто его отправил.
Почему позиция в сети дороже ответа?
Потому что до атакующего ответ обычно не доходит, а атака всё равно работает.
Обработчик доставки читает код ответа и выбрасывает тело. В дашборд ничего не возвращается, поэтому тот, кто зарегистрировал адрес, узнаёт по одному биту за попытку: соединение приняли или не приняли. Это слепой вариант, и команды, которые думают про SSRF как про кражу данных, его недооценивают. Один бит, повторённый много раз, рисует карту сети.
Ценность тут в позиции. Внутри периметра обработчик доезжает до хостов, которых никто не строил под запросы снаружи, и многие из них родом из времени, когда быть во внутренней сети и значило пройти аутентификацию. Снаружи периметра исходящий адрес платформы нередко лежит в чьём-то белом списке: у партнёра, согласившегося принимать трафик от вас и больше ни от кого.
Платёжную платформу впереди сборщика PDF ставит лестница повторов. Отправитель, который повторяет по нарастающей задержке, превращает одну регистрацию в задачу: она стреляет снова и снова, на чужой инфраструктуре.
Где должна стоять проверка?
После разрешения имени и до открытия соединения, в месте, куда в большинстве HTTP-библиотек неудобно встать.
Фильтр по строке пишут первым, потому что для него не надо выходить из обработчика запроса. Ломает его само устройство имён: хост — это указатель, и куда он указывает, решает DNS-запись, которой вполне может владеть тот, кто регистрирует адрес. Чтение текста не говорит ничего о том, куда уйдёт пакет. Руководство OWASP по предотвращению SSRF требует получить адреса за именем, A и AAAA, и применить к ним те же правила, что и к адресу, введённому напрямую.
Из-за этого неудобства наивная версия и продолжает выходить в релизы. Библиотека-клиент хочет взять URL и вернуть ответ, а нужная вам зацепка сидит между двумя шагами, которые она считает своим внутренним делом. Добраться туда значит разрешать имя самому или вмешиваться в создание сокета, и то и другое дороже регулярного выражения по полю формы.
Какие диапазоны обязан отклонять список?
Больше, чем помнит наизусть средний инженер.
Обратная петля и частные блоки RFC 1918 очевидны. Туда же идут локальные адреса канала: в этом диапазоне отвечают метаданные облачных машин, и попасть туда значит отдать учётные данные. Два диапазона выпадают из списков чаще прочих, и оба моложе той модели, из которой люди пишут по памяти.
Общее адресное пространство 100.64.0.0/10 зарезервировано RFC 6598 в апреле 2012 года под carrier-grade NAT, и сам RFC отделяет его от RFC 1918, потому что оно принадлежит сетям операторов связи. Фильтр, сопоставляющий строки 10. и 192.168., про него мнения не имеет, а заодно теряет половину RFC 1918 по дороге.
Уникальные локальные адреса IPv6, fc00::/7, приходят из RFC 4193, где определены как не маршрутизируемые в глобальном интернете и маршрутизируемые внутри площадки. Вебхук не должен уходить ни на что, подходящее под это описание, а список диапазонов, написанный только под IPv4, о них не вспоминает.
Доставка вебхуков Paymos отбрасывает 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, три документационных диапазона 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 и fc00::/7; обратная петля и локальный адрес канала IPv6 проверяются отдельно. Адрес IPv4, записанный в виде IPv6, приводится к общему виду до проверки — это закрывает классический обход. По редиректам доставка не идёт. URL вебхука принимается только на HTTPS: другую схему не примут ни при создании адреса, ни при его изменении.
Проверка адресов включена. Называть её неотключаемой было бы преувеличением — это настройка. Отказ от редиректов от неё не зависит и держится в любом случае. Узкое утверждение о безопасности стоит дороже широкого, потому что узкое можно проверить.
Почему редирект обходит уже пройденную проверку?
Потому что URL, который вы проверили, перестаёт быть тем URL, который вы запрашиваете.
Обработчик разрешает имя, получает публичный адрес, одобряет его, открывает соединение. Сервер отвечает 302 с новым Location. Если HTTP-клиент ходит по редиректам, а HttpClient в .NET и requests в Python ходят, пока им не сказать обратного, то второй запрос не проверял никто: проверка была событием в жизни первого.
Руководство OWASP закрывает это одной фразой: отключите поддержку перенаправлений в своём веб-клиенте. Сказано грубо и сказано верно. Адресу, который принимает обратные вызовы, незачем передавать их дальше, а мерчант, переехавший на новое место, зарегистрирует новый адрес.
Рядом живёт тихая версия той же беды, и она работает, даже когда никто никуда не перенаправляет. У DNS-ответа есть срок жизни, а одобрение вы выдали тому ответу, который видели. Закрывает это устройство клиента; правилом внутри проверки такое не дописать. В Paymos имя разрешается один раз, соединение прибивается к проверенному адресу, и второго запроса в DNS между проверкой и сокетом не происходит, поэтому окна на подмену DNS-ответа (DNS rebinding) не остаётся.
Как выглядит отказ, который ничего не стоит?
Как отказ до соединения и как бюджет, который этими отказами не вычерпать.
Первую половину несёт порядок действий. Адрес, не прошедший проверку, отбрасывается до любой попытки соединения, поэтому заблокированная регистрация не тратит исходящую ёмкость вовсе.
Вторая половина — ограничения ресурсов, которые обычно пропускают, потому что выглядят как забота о надёжности. На доставке вебхуков Paymos их и делали ради надёжности, но границу они ставят и здесь:
- Десять секунд на попытку. Без крайнего срока точка назначения, которая отвечает на рукопожатие и замолкает, держит обработчик, пока не кончится что-нибудь ещё, а десяток таких останавливает разбор очереди.
- Одиннадцать попыток закрывают событие: первая плюс десять повторов, с задержками от минуты до восьми часов, примерно за шестнадцать часов. Повторы без потолка выдали бы выбранной точке назначения источник трафика, который перезаряжает себя сам.
- Десять адресов на среду мерчанта. Предел на регистрацию и стоит между одним аккаунтом и неограниченной долей исходящей ёмкости.
Эти попытки ограничивают событие и никогда не адрес. Исчерпанный цикл помечает событие неудавшимся и оставляет регистрацию живой, так что приёмник, лежавший сутки, возвращается к событиям, готовым к переотправке.
Чем эта защита оборачивается для мерчанта?
Настоящие адреса на ней ломаются, и поломка выглядит как сбой, а не как сработавший контроль.
Обратный вызов не поставить на localhost или на машину в офисной сети, поэтому локальной разработке нужен туннель, который выставит перед обработчиком публичное имя. С этим сталкиваются в первый же день.
Отказ от редиректов означает, что зарегистрированный URL должен быть конечным. Адрес, отвечающий 301 с голого домена на www, или путь, переехавший в прошлом году и до сих пор перекидывающий дальше, доставку валит, и при этом в браузере по тому же адресу открывается здоровая страница. Сокращатели ссылок и обёртки почтовых фильтров перед обратным вызовом — та же беда в другом обличье.
Тоньше всего история с DNS-записью, направленной не туда. Когда A-запись имени несёт тот адрес, который роутер показывает изнутри, а не тот, что видит мир, точка назначения попадает в заблокированный диапазон, и наружу не уходит ничего.
Ни одна из этих поломок о себе не объявляет. Мерчант видит, что вебхуки не приходят, и открывает обращение в поддержку; первым делом идут отлаживать обработчик, а ответ лежит в зарегистрированном адресе.
Что спрашивать у платёжного процессинга?
Спросите, где стоит проверка. Остальные ответы следуют из этого.
Поставщик, проверяющий разрешённый адрес, скажет это без наводящих вопросов и назовёт диапазоны. Обратная петля и RFC 1918 — простая половина. Ответ, доходящий до общего адресного пространства и уникальных локальных адресов IPv6, написан тем, кто читал действующие RFC, а не переписывал старый чек-лист. Спросите, ходит ли доставка по редиректам. Спросите, во что отклонённая точка назначения обходится исходящей ёмкости, которую вы делите с другими мерчантами.
Потом прочитайте страницу безопасности поставщика и проверьте, что она заканчивается там же, где заканчивается инженерная часть. Страница, которая обещает больше, чем делает код, — риск другого рода, и как раз его видно снаружи.
Если вы по другую сторону и строите отправителя, самое трудное в этом контроле — что он молчит, когда работает, и выглядит багом, когда срабатывает. Сделайте отказ таким, чтобы он называл правило, в которое упёрся, и скажите это мерчанту в ответе API, а потом уже своему журналу.
| Где стоит проверка | Что она видит | Что всё равно проходит | |
|---|---|---|---|
| По строке URL | Текст, который клиент вписал в форму | Имя, чья DNS-запись ведёт внутрь | |
| По имени хоста | Знакомо ли вам это имя | Любое незнакомое имя, куда бы оно ни разрешилось | |
| По разрешённым адресам | Все записи A и AAAA за именем | Редирект в ответе | |
| На каждом соединении, каждую попытку | Адрес, к которому идёт этот запрос | То, что не попало в список диапазонов |
Частые вопросы
Что такое SSRF через вебхуки?
Подделка запроса со стороны сервера через поле адреса обратного вызова. Точку назначения выбирает клиент, а идёт туда фоновый обработчик внутри вашей сети, и запрос получает сетевую позицию, которой у клиента нет.
Можно ли указать в адресе вебхука localhost для тестов?
У отправителя, который блокирует обратную петлю, нет. Paymos отклоняет обратную петлю, частные диапазоны, локальные адреса канала, CGNAT и IPv6 ULA, поэтому локальной разработке нужен туннель с публичным именем перед обработчиком.
Ходит ли доставка Paymos по редиректам?
Нет. Автоматические HTTP-редиректы не выполняются, поэтому зарегистрированный адрес должен быть конечным и не перекидывать дальше.
Хватит ли заблокировать 127.0.0.1?
Нет. Имя разрешается туда, куда указывает его DNS-запись, поэтому проверять надо разрешённые адреса, а список отказа должен доходить до частных диапазонов, локальных адресов канала, carrier-grade NAT и уникальных локальных адресов IPv6.
Почему вебхуки перестали приходить после смены адреса?
Адрес из заблокированного диапазона отбрасывают до соединения, а редирект в ответе засчитывают неудачной попыткой. Проверьте, что имя разрешается в публичный адрес, что схема HTTPS и что URL конечный.
Источники
- 1. OWASP Top 10:2025 — A01 Broken Access Control (accessed 2026-09-15)
- 2. OWASP — Server Side Request Forgery Prevention Cheat Sheet (accessed 2026-09-15)
- 3. RFC 6598 — IANA-Reserved IPv4 Prefix for Shared Address Space (accessed 2026-09-15)
- 4. RFC 4193 — Unique Local IPv6 Unicast Addresses (accessed 2026-09-15)
- 5. Microsoft Learn — HttpClientHandler.AllowAutoRedirect (accessed 2026-09-15)
- 6. Документация Paymos — безопасность (accessed 2026-09-15)
Последняя проверка: 15 сент. 2026 г.


