网安资讯详情 - SecLens 情报雷达

网安资讯,一网打尽。汇集权威漏洞通告与行业要闻,结合分组浏览、智能过滤、RSS订阅 和 Webhook 推送,多通道拓展您的安全情报视野。

npm/@frontmcp/adapters: FrontMCP: Server-Side Request Forgery (SSRF) in the OpenAPI adapter spec-change poller

来源: github_advisory · 发布时间 2026-07-25 06:40 (UTC+08:00) · 抓取时间 2026-07-25 06:45 (UTC+08:00)

原文链接

摘要

Severity: MEDIUM | CVSS: 5.9 | Package: npm/@frontmcp/adapters | Affected: <= 1.5.5 | Patched: 1.5.6

正文

## Summary The OpenAPI adapter's spec-change **poller** (`OpenApiSpecPoller`) re-fetched the configured spec `url` on a timer using a raw global `fetch()`, bypassing the SSRF guard (`safeFetch` / `assertUrlSafe`) that `OpenAPIToolGenerator.fromURL()` applies to the initial spec load. As a result, the pinning/DNS-resolution hardening delivered via `mcp-from-openapi >= 2.5.0` (advisory GHSA-65h7-9wrw-629c) protected the initial load but **not** the recurring poll of the same URL. When polling is enabled against an untrusted or attacker-influenceable spec URL, this is an unguarded SSRF vector. ## Details The initial spec load is guarded. `OpenapiAdapter` resolves a secure `refResolution` policy and passes it to the guarded loader: ```ts // libs/adapters/src/openapi/openapi.adapter.ts — initializeGenerator() return await OpenAPIToolGenerator.fromURL(this.options.url, { // ... followRedirects: this.options.loadOptions?.followRedirects ?? false, refResolution, // secure default: external $refs off, internal targets blocked }); ``` But the poller — which re-fetches **the same URL** on every interval — did not: ```ts // libs/adapters/src/openapi/openapi-spec-poller.ts — doFetch() (vulnerable, <= 1.5.5) const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs); try { const response = await fetch(this.url, { // <-- raw global fetch, no SSRF guard headers, signal: controller.signal, }); // ...hash the body, fire onChanged... } ``` Because `doFetch()` never called `safeFetch`, none of the guard's protections applied to the polled request: - no allow-list / block-list enforcement (`allowedHosts` / `blockedHosts`); - no internal/private/loopback/link-local/CGNAT/cloud-metadata IP blocking; - no DNS resolution of the hostname (so a DNS name that resolves to an internal IP, e.g. `http://127.0.0.1.nip.io/`, was reached); - no connection **pinning** to the validated IP (DNS-rebinding TOCTOU); - no per-hop re-validation of HTTP redirects. This is the identical threat model to `fromURL()` / external `$ref` resolution (GHSA-65h7-9wrw-629c), applied to a request path that the fix for that advisory did not cover. ## Impact A server that enables spec polling against an untrusted or attacker-influenceable spec URL will, on every poll interval, issue a server-side `GET` to whatever host the URL (or a DNS name it resolves to, or a redirect it returns) points at — including internal-only addresses unreachable from the public internet. Consequences include: - reading cloud-instance metadata endpoints (e.g. `169.254.169.254`) — credential / token theft; - probing and reaching internal services and private-range hosts (internal network scanning); - DNS-rebinding to swap a public host for an internal one between validation and connection. The poller issues `GET` requests only, so the primary impact is **confidentiality** (reaching and reading internal endpoints); the fetched body is content-hashed to detect change and the subsequent tool rebuild goes back through the guarded `fromURL()` path. ## Preconditions Exploitation requires **both**: 1. `polling.enabled: true` on an `OpenapiAdapter` (polling is off by default and requires the URL-based `url` option, not an inline `spec`); **and** 2. the spec `url` is untrusted / attacker-influenceable (e.g. it is derived from user input, a tenant-supplied value, or otherwise not a fixed trusted constant), or an otherwise-trusted spec host is attacker-controlled or can redirect. Servers that poll a fixed, trusted, first-party spec URL are not exposed in practice, though they still benefit from the guard as defense-in-depth. ## Proof of concept ```ts import { OpenapiAdapter } from '@frontmcp/adapters'; // url is attacker-influenceable and points (directly, via DNS, or via redirect) // at an internal target; polling re-fetches it every interval. const adapter = OpenapiAdapter.init({ name: 'evil', url: 'http://169.254.169.254/latest/meta-data/', // or http://127.0.0.1.nip.io/... polling: { enabled: true, intervalMs: 5000 }, }); await adapter.fetch(); // initial load IS guarded (blocked) adapter.startPolling(); // <= 1.5.5: each poll issues an UNGUARDED GET to the internal target ``` On `<= 1.5.5` the timed poll reaches the internal address. On the patched version the poll fails closed (no request is made; the failure is logged) exactly as the initial load does. ## Patch The fix routes the poller through the same SSRF guard as the initial load, with the same policy, so both paths share one DNS resolution + connection pinning and cannot diverge: - `OpenApiSpecPoller.doFetch()` now calls `safeFetch(this.url, { headers, timeoutMs, followRedirects, ssrf })` from `mcp-from-openapi` instead of the global `fetch()`. - `OpenapiAdapter.startPolling()` injects the adapter's resolved policy into the poller: `ssrf: normalizeSsrfOptions(this.resolveRefResolution())` and `followRedirects: loadOptions?.followRedirects ?? false` — identical to what `fromURL()` receives. - `SpecPollerOptions` gained optional `ssrf` / `followRedirects`; standalone use of `OpenApiSpecPoller` defaults to the secure policy (internal targets blocked, redirects not followed). Files changed: - `libs/adapters/src/openapi/openapi-spec-poller.ts` - `libs/adapters/src/openapi/openapi-spec-poller.types.ts` - `libs/adapters/src/openapi/openapi.adapter.ts` Requires `mcp-from-openapi >= 2.5.0` (already a dependency at `2.5.1`), which exports `safeFetch` / `normalizeSsrfOptions` and performs the resolved-IP validation and connection pinning. ## Remediation Upgrade `@frontmcp/adapters` to `1.5.6` or later. No configuration change is required: polling now inherits the same secure defaults as the initial spec load (external targets blocked, redirects not followed). To poll a genuinely internal or localhost spec server in a trusted environment, opt in explicitly with `loadOptions.refResolution.allowInternalIPs: true` — the same knob that gates the initial load. ## Workarounds For users who cannot upgrade immediately: - disable polling (`polling.enabled: false`) on adapters whose spec `url` is not a fixed, trusted, first-party value; or - only enable polling against spec URLs you fully control, served over HTTPS from a host that cannot be made to redirect to internal targets; and - enforce network egress controls / an allow-list at the platform layer so the server cannot reach internal ranges or cloud-metadata endpoints.

标签

扩展字段

{
  "credits": [
    {
      "login": "EchoSkorJjj",
      "type": "finder"
    },
    {
      "login": "frontegg-david",
      "type": "remediation_developer"
    }
  ],
  "cvss_score": 5.9,
  "cvss_vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
  "cwe_ids": [
    "CWE-918"
  ],
  "cwe_names": [
    "Server-Side Request Forgery (SSRF)"
  ],
  "ghsa_id": "GHSA-8q49-2h5h-434x",
  "package": {
    "ecosystem": "npm",
    "name": "@frontmcp/adapters",
    "patched_version": "1.5.6",
    "vulnerable_range": "<= 1.5.5"
  },
  "references": [
    "https://github.com/agentfront/frontmcp/security/advisories/GHSA-8q49-2h5h-434x",
    "https://github.com/agentfront/frontmcp/pull/510",
    "https://github.com/agentfront/frontmcp/commit/077201e109bf6f45dbc85c36d6bd77ded18ab13e",
    "https://github.com/agentfront/frontmcp/releases/tag/v1.5.6",
    "https://github.com/advisories/GHSA-8q49-2h5h-434x"
  ],
  "source_code_location": "https://github.com/agentfront/frontmcp",
  "updated_at": "2026-07-24T22:40:00Z"
}