FlawOpen

flawopen.com/Teardowns/cve-2026-14540-google-mcp-toolbox-ssrf

● CVE-2026-14540 · CVSS 8.0 · Высокий
Исследования · FlawOpen

CVE-2026-14540: SSRF в Google MCP Toolbox

CVE-2026-14540 позволяла HTTP-запросам MCP Toolbox следовать перенаправлениям без проверки конечного сетевого адреса. Обновите версию до 1.5.0 или новее.[1], [3]

💡 Простыми словами (ELI5)

Почтовый отдел проверяет первый адрес на посылке, а затем следует любой наклейке о пересылке, не проверяя новый пункт назначения. HTTP-перенаправление работает так же: без проверки конечного IP запрос к обычному сайту может попасть во внутреннюю сеть. Исправление проверяет фактический адрес при подключении и отдельно проверяет адреса перенаправлений.[2], [4], [7]

Ключевые понятия и термины

Server-side request forgery (SSRF)
Сервис вынуждают отправить HTTP-запрос к адресу, на который влияет атакующий. Запрос исходит от сервера и поэтому может попасть в сети, недоступные инициатору.[6]
HTTP-перенаправление
Ответ может указать HTTP-клиенту отправить следующий запрос на другой URL. Проверка только исходного URL не ограничивает адрес следующего запроса.[4], [7]
DNS rebinding и TOCTOU
Во время проверки имя хоста может разрешиться в один адрес, а при открытии сокета — в другой. Проверка разрешённого IP непосредственно при подключении закрывает разрыв между проверкой и использованием.[2], [7]
Generic HTTP source и tool
Компоненты MCP Toolbox, формирующие HTTP-запросы из настроенных конечных точек и параметров инструментов. В уведомлении этот путь указан как затронутый.[1], [2], [4]
CWE-918
Server-Side Request Forgery (SSRF): приложение получает URL или обращается к адресу, не ограничивая должным образом возможное направление запроса.[6]

Связи со стандартами

  • Ссылка CWE для слабого места, описанного в этом руководстве: CWE-918
  • D3-OTF Фильтрация исходящего трафика — Описанное в руководстве ограничение исходящего сетевого трафика сокращает список адресов, доступных сервису или хосту.

Анализ первопричины

В уязвимых версиях generic HTTP source создавала Go http.Client с тайм-аутом и транспортом, но без политики перенаправлений с проверкой назначения и без проверки IP на границе сокета. По умолчанию Go следовал перенаправлениям. Настроенные HTTP-инструменты могли включать параметры в запросы, поэтому специально выбранное значение или перенаправление могли увести запрос от ожидаемого адреса. Проверки только исходного URL недостаточно: перенаправление меняет адрес, а DNS может вернуть другой IP между проверкой и подключением. В исправлении добавлен SSRFGuard: он отклоняет запрещённые назначения в CheckRedirect и повторно проверяет разрешённый IP на уровне подключения. Частные, loopback, link-local и другие неглобальные адреса по умолчанию блокируются; разрешение диапазонов или частных сетей является исключением настройки и должно быть строго ограничено.[1], [2], [4]

Пошаговый сценарий атаки

Шаг 1

Проверьте достижимость по трём условиям

Для этой цепочки нужны все три условия: (1) уязвимая версия 0.3.0–1.4.0 использует generic HTTP source/tool; (2) вызывающий может обратиться к этому пути и управлять URL/путём либо вызвать перенаправление; (3) узел Toolbox может маршрутизировать трафик к цели. Если хотя бы одного условия нет, эта цепочка не достигнет цели через данный путь. Собственные механизмы аутентификации и контроля доступа цели продолжают действовать.[1]

Шаг 2

Параметр инструмента влияет на исходящий запрос

Настроенный HTTP-инструмент объединяет базовую конечную точку со входными значениями. В описании CVE указано, что специально сформированные параметры пути могут перенаправить HTTP source на другой адрес. Пользователь или агент MCP вызывает инструмент; недоверенное содержимое влияет на этот вызов только тогда, когда это допускает окружающий процесс.[1], [2]

Шаг 3

Исходный URL не ограничивает перенаправления

В уязвимом клиенте не было ограничивающего hook CheckRedirect и проверки IP назначения. Ответ может указать другой URL, поэтому проверка только базового или первого запроса не ограничивает следующий запрос.[1], [2]

Шаг 4

Toolbox отправляет запрос из собственной сети

Следующий запрос исходит от хоста Toolbox и может обращаться к адресам, разрешённым сетью этого хоста. Внутренние сервисы или другие доступные точки могут получить запросы, которые пользователь MCP не мог бы отправить напрямую. CVE не доказывает, что в каждом развертывании раскрываются облачные учётные данные или что каждое перенаправление успешно.[1]

Шаг 5

Обновите версию и проверьте исходящий трафик

Обновите MCP Toolbox до 1.5.0 или новее. Исправление проверяет настроенные IP-литералы и перенаправления, а при подключении блокирует запрещённые разрешённые IP, что устраняет и DNS rebinding. Оставьте allowPrivateNetworks отключённым без необходимости, строго ограничьте разрешённые диапазоны и отдельно применяйте политику сетевого выхода.[2], [3], [4]

Исходный код: Уязвимый vs Защищённый вариант

Здесь показан выбранный фрагмент upstream-пути HTTP-клиента, а не замена всего файла. Влитое исправление добавляет SSRFGuard: он проверяет назначения перенаправлений и отклоняет запрещённые разрешённые IP непосредственно при подключении, закрывая уязвимость DNS rebinding, которую оставила бы одна проверка перенаправления. По умолчанию guard блокирует частные и другие неглобальные адреса. allowPrivateNetworks, allowedIpRanges и customBlockedIpRanges — мощные настройки; исключение в allowlist следует ограничивать только нужным диапазоном. Исходный файл: internal/sources/http/http.go.[2]

Фрагмент исходного кода: Фрагмент HTTP-клиента и политики перенаправления

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
client := http.Client{
	Timeout: duration,
	Transport: tr,
}

// Без политики CheckRedirect клиент следует HTTP-перенаправлениям по
// умолчанию. Транспорт также не ограничивает адреса назначения для защиты от SSRF.
БЕЗОПАСНЫЙ ИСПРАВЛЕННЫЙ ВАРИАНТ
client := &http.Client{
	Timeout: duration,
	Transport: tr,
	CheckRedirect: func(req *http.Request, via []*http.Request) error {
		if len(via) >= 10 {
			return fmt.Errorf("stopped after 10 redirects")
		}
		hostname := req.URL.Hostname()
		if ip := net.ParseIP(hostname); ip != nil {
			if guard.IsIPBlocked(ip) {
				return fmt.Errorf("redirect to blocked IP %s denied", ip)
			}
			return nil
		}
		addrs, err := resolver.LookupHost(req.Context(), hostname)
		if err != nil {
			return fmt.Errorf("failed to resolve redirect host %s: %w", hostname, err)
		}
		for _, addr := range addrs {
			if ip := net.ParseIP(addr); ip != nil && guard.IsIPBlocked(ip) {
				return fmt.Errorf("redirect host %s resolves to blocked IP %s", hostname, addr)
			}
		}
		return nil
	},
}
// Тот же SSRFGuard применяется и на границе открытия сокета,
// поэтому DNS-ответ не может заменить разрешённый адрес заблокированным.

Чек-лист по защите системы для инженеров

Источники