Назад в блог

SSRF в MCP: как защитить OAuth discovery и внутреннюю сеть

Как закрыть SSRF в MCP: проверить OAuth discovery, DNS и редиректы, разделить внешние и внутренние подключения и провести приёмку защиты.

Иллюстрация к материалу: SSRF в MCP: как защитить OAuth discovery и внутреннюю сеть

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

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

Материал рассчитан на разработчиков MCP-клиентов, платформенные команды и специалистов по безопасности. Первичные источники проверены 6 октября 2026 года. Ниже предложена архитектура защиты и учебные сценарии приёмки, а не описание обнаруженной уязвимости Codenik или обещание конкретного набора функций продукта.

Почему OAuth discovery становится входом для SSRF

Клиент должен выяснить, как авторизоваться на защищённом сервере. Для этого он читает документы с адресами следующих шагов. В официальных Security Best Practices MCP отдельно описан риск SSRF через OAuth discovery: источниками адресов становятся resource_metadata, authorization_servers и поля метаданных сервера авторизации. Документ рекомендует учитывать сетевое окружение клиента и защищать обращения по этим адресам.

Представим корпоративный сервис, где сотрудники добавляют MCP-интеграции через веб-интерфейс. Веб-интерфейс передаёт URL серверному процессу. Процесс работает рядом с внутренними приложениями и умеет обращаться к ним по сети. Если каждый адрес из полученного JSON он принимает как доверенный, внешняя интеграция получает косвенный сетевой маршрут через этот процесс.

Здесь важно различать две операции. Браузер пользователя открывает страницу согласия, а серверный клиент загружает метаданные или обращается к token endpoint. Они выполняются в разных сетях и с разными правами. Успешное открытие страницы в браузере ничего не доказывает о безопасности запроса из серверного окружения.

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

Где провести границу доверия

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

Одобрение лучше выражать не флагом «доверенный сервер», а объектом подключения. Такой объект связывает организацию, владельца, исходный адрес, допустимые назначения и тип сети. Его идентификатор приходит в исполнитель вместе с заданием. Серверные ограничения читаются из этого объекта, а не из аргументов, которые сгенерировала модель.

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

Отдельно ограничьте право менять объект подключения. Агенту, который читает задачи, обычно не нужен инструмент, способный заменить OAuth endpoint. Иначе запрет внутри рабочего инструмента обходится через настройку интеграции. Такие изменения должны иметь своего владельца и самостоятельный аудит.

Учебный сценарий: внешняя интеграция и служебный адрес

Допустим, сотрудник подключает внешний каталог документации. Сам адрес каталога разрешён, сертификат корректен, ответ содержит ожидаемый JSON. В одном из полей следующий endpoint указывает на служебный адрес, доступный только серверному процессу. Уязвимый клиент начинает вторую загрузку автоматически, потому что считает discovery частью одного доверенного подключения.

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

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

Третий контроль ограничивает последствия. Процесс discovery не получает универсальных токенов внутренних API. Даже ошибочный запрос не должен автоматически нести привилегированный заголовок. Это не замена сетевой защите, а способ не превращать ошибку маршрутизации в передачу полномочий.

Как проверять URL без самодельного парсера

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

В OWASP SSRF Prevention Cheat Sheet рассматриваются allowlist, проверка адресов и сетевые ограничения. Это полезная основа для выбора защиты, однако конкретные исключения зависят от того, должна ли ваша система обращаться только к известным интеграциям или принимать новые внешние назначения.

Ниже пример записи политики, а не конфигурация готового MCP SDK или Codenik:

connection_policy:
  class: public_integration
  schemes: [https]
  ports: [443]
  credentials_in_url: reject
  redirects: validate_each_hop
  private_destinations: deny
  network_route: restricted_egress
  response_limits:
    max_bytes: 262144
    total_timeout_seconds: 10

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

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

Почему DNS нужно связать с фактическим соединением

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

Практическая задача команды — выбрать сетевой компонент, который умеет обеспечить эту связь. Это может быть контролируемый dialer или исходящий прокси с соответствующей политикой. Простая проверка «резолвим домен перед fetch» сама по себе не доказывает отсутствие расхождения между проверкой и использованием.

Сохранение проверенного IP тоже требует аккуратности. TLS должен продолжать проверять ожидаемое имя сервера. Если разработчик заменяет URL адресом сокета и отключает проверку сертификата ради работоспособности, он добавляет другую проблему. Сетевая библиотека должна разделять имя для TLS и адрес соединения предусмотренным способом.

При нескольких DNS-ответах заранее определите правило принятия решения. Например, для внешнего профиля можно отклонять назначение, если набор содержит запрещённый адрес. Затем проверьте, что библиотека не выполнит незаметный fallback к адресу, который политика не разрешила. Это относится и к повтору запроса после сетевой ошибки.

Редиректы, прокси и повторные запросы

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

Особое внимание уделите заголовкам. Заголовок авторизации для одного назначения не должен безусловно переноситься на другое. Даже когда оба домена публичные, они могут принадлежать разным сторонам. В учебной приёмке полезен отдельный сервер-получатель, который показывает, дошёл ли до него тестовый маркер заголовка после перехода.

Прокси добавляет ещё одного участника. Проверяйте, где происходит DNS-разрешение: в приложении или у прокси. Проверка IP в приложении бесполезна, если прокси выбирает адрес заново без своей политики. Также изучите, какие переменные окружения HTTP-библиотека читает автоматически и кто может менять их в исполнительном окружении.

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

Как разрешить внутренний MCP без общего исключения

Корпоративный MCP-сервер нередко находится в частной сети. Общий запрет частных адресов тогда мешает полезному сценарию. Решение — отдельный класс внутренних подключений, где разрешается конкретный сервис через конкретный маршрут. Публичное подключение не должно иметь возможности объявить себя внутренним.

Запись внутреннего подключения может включать организацию, сервис, допустимый порт, окружение и владельца. Выбор сервиса лучше делать из административного каталога. Для пользователя это понятный пункт «Корпоративная база знаний», а не предложение вручную перечислять подсети. Технические параметры остаются у платформенной команды.

Важно ограничить маршрут по назначению. Если рабочему процессу нужен один внутренний API, это не означает потребность в доступе ко всем приложениям того же сегмента. Сетевой профиль discovery может отличаться от профиля рабочих вызовов. Сервер авторизации, сам MCP-сервер и корпоративный API иногда находятся в разных инфраструктурах.

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

Изоляция секретов и ограничение ответов

Обработка метаданных не требует выдавать процессу подключения все секреты организации. Секрет для конкретной интеграции должен подставляться только на нужном шаге и только в запрос к согласованному назначению. Для первых загрузок, где секрет ещё не нужен, полезно использовать клиент без привилегированных заголовков вообще.

Ответ внешнего сервера тоже следует ограничивать. Размер тела, длительность чтения, число вложенных объектов и формат документа влияют на доступность клиента. Учитывайте сжатие: маленькое сообщение на транспортном уровне может превратиться в большой объект после распаковки. Предел должен применяться к реально обрабатываемым данным.

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

Разделение сетевой защиты и контроля данных помогает правильно расследовать сбой. Запрос мог уйти к разрешённому сервису, но ответ оказался неприемлемым по формату. Или адрес был запрещён до подключения. Эти события требуют разных действий, поэтому их нельзя сводить к одному сообщению «MCP недоступен».

Приёмка защиты: что проверять и как читать результат

Соберите небольшой управляемый стенд: внешний тестовый MCP, тестовый сервис авторизации, внутренний приёмник запросов и журнал исходящего прокси. Используйте только принадлежащие команде системы. Цель — подтвердить границы, а не исследовать чужие сети. Каждый сценарий должен иметь заранее ожидаемый результат на всех слоях.

СценарийОжидаемое поведениеДоказательство
Разрешённые внешние метаданныеПодключение проходитУспешный запрос и корректный разбор
Запрещённое назначение в следующем документеОтказ до обращенияНоль обращений к тестовому приёмнику
Разрешённый адрес меняет назначение редиректомНовый адрес проверяетсяРешение политики для второго шага
DNS меняется между проверкой и соединениемЗапрещённый адрес недоступенЖурнал фактического соединения
Ответ превышает пределЧтение прекращаетсяТипизированная ошибка и ограниченный расход
Внутреннее административное подключениеДоступен только выбранный сервисУспех цели и отказ соседнему сервису

У проверки три возможных исхода: подтверждено, обнаружено нарушение, не удалось проверить. Если тестовый приёмник не запустился, отсутствие обращений ничего не доказывает. Перед основными сценариями отправьте к нему разрешённый контрольный запрос и убедитесь, что счётчик действительно работает.

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

Эксплуатация: владелец, журнал и изменения политики

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

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

После изменения HTTP-библиотеки повторите сценарии редиректов, DNS и заголовков. Эти детали могут зависеть от реализации клиента. После смены прокси повторите проверку сетевого слоя. После добавления новой ветки OAuth проверьте, что она использует общий защищённый путь загрузки, а не отдельный вспомогательный fetch.

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

Где здесь место Codenik

Codenik полезно рассматривать как управляемый слой доступа между AI-агентами и корпоративными системами. Для задачи SSRF ценность такого слоя — возможность обсуждать подключения, права и секреты как отдельные объекты управления. Нельзя выводить наличие конкретной защиты только из того, что в архитектуре появился шлюз.

При выборе решения попросите показать путь подключения целиком: кто загружает discovery, где проверяется назначение, как отделён внутренний маршрут, какие права имеет исполнитель и что видно в журнале. Общая схема «агент → шлюз → API» недостаточна. Решение следует оценивать по демонстрации отказов и успешных сценариев на согласованном стенде.

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

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

Как разобрать отказ без ослабления защиты

Рассмотрим ещё один учебный случай. После включения политики внешний каталог перестал подключаться. В журнале видно, что discovery дошёл до разрешённого домена, но следующий endpoint использует другой порт. Менеджер интеграции предлагает временно разрешить все порты: работа срочная, поставщик известен. Для платформенной команды полезнее выяснить, какой именно запрос нужен и соответствует ли он ожидаемой архитектуре поставщика.

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

Затем сверяйте адрес с официальной документацией поставщика или через согласованный контакт владельца интеграции. Указание в самом discovery не является независимым подтверждением. Если новый endpoint легитимен, можно разрешить точное назначение в политике этого подключения. Разрешение всех портов для всех внешних серверов не соответствует причине сбоя и увеличивает область доступа остальных интеграций.

Перед применением исключения проверьте обе стороны результата. Нужное подключение должно успешно авторизоваться, а контрольный запрос к соседнему запрещённому назначению должен по-прежнему отклоняться. Запишите версию политики до и после изменения. Так оператор сможет объяснить, какой доступ появился, и воспроизвести решение, если инфраструктура поставщика снова поменяется.

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

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

FAQ и план первого внедрения

Достаточно ли разрешить только HTTPS? Нет. Такой выбор определяет защищённый транспорт, но не разрешённую сторону соединения. Нужны ограничения назначения и фактического сетевого маршрута. Дополнительно проверьте переходы между адресами и отсутствие автоматического переноса секретных заголовков.

Можно ли просто запрещать частные IP? Для публичных подключений это важный слой, но корпоративным интеграциям иногда нужен внутренний сервис. Используйте разные профили. Не расширяйте публичный профиль общим исключением ради одного внутреннего MCP и не позволяйте удалённым метаданным выбирать профиль.

Нужно ли проверять адрес после успешного подключения? Да, когда появляется новое сетевое назначение или новый запрос. DNS, редиректы и инфраструктура поставщика могут изменяться. Одобрение подключения задаёт политику, а не бессрочный пропуск для любого адреса, объявленного сервером.

Это задача модели или приложения? Контроль сетевого доступа должен выполняться приложением и инфраструктурой. Модель может объяснить отказ или помочь оператору, но промпт не способен гарантировать, к какому адресу подключится HTTP-клиент. Для подробностей авторизации полезно отдельно прочитать руководство по MCP OAuth.

Начните с карты загрузок discovery и двух профилей подключений: публичного и адресного внутреннего. Затем объедините загрузки в один защищённый путь, подключите сетевой контроль и подготовьте тестовый приёмник. Завершите внедрение доказательствами по каждому сценарию, инструкцией для владельца исключений и перечнем изменений, после которых приёмку нужно повторять.

Источники

Дата проверки: 6 октября 2026 года. Таблица приёмки, политика YAML и сценарий внедрения — авторские рекомендации, не конфигурация SDK и не результаты испытания продукта.