Skip to content

[Security] Server-Side Request Forgery in built-in web_fetch tool allows access to special-use and internal HTTP destinations #2889

@YLChen-007

Description

@YLChen-007

Advisory Details

Title: Server-Side Request Forgery in built-in web_fetch tool allows access to special-use and internal HTTP destinations

Description:

Summary

The built-in web_fetch tool accepts attacker-influenced http and https URLs and issues server-side requests without validating the resolved destination address. An attacker who can drive the agent tool plane can cause the CowAgent process to send HTTP requests to special-use, loopback, private, or other internal network destinations. I verified this against the real WebFetch implementation with an integration PoC that observed an outbound request targeting 198.18.0.1:18080.

Details

The vulnerable logic is in agent/tools/web_fetch/web_fetch.py.

WebFetch.execute() only checks that the supplied URL uses http or https. It does not classify the destination host or any resolved IP before dispatching the request:

parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
    return ToolResult.fail("Error: Invalid URL (must start with http:// or https://)")

After that, the tool directly performs outbound requests with redirects enabled:

response = requests.get(
    url,
    headers=DEFAULT_HEADERS,
    timeout=DEFAULT_TIMEOUT,
    allow_redirects=True,
)

The same missing destination validation also exists in the document download path:

response = requests.get(
    url,
    headers=DEFAULT_HEADERS,
    timeout=DEFAULT_TIMEOUT,
    stream=True,
    allow_redirects=True,
)

This means the tool enforces a scheme allowlist, but not a destination allowlist. In practice, if a user prompt or tool call can influence args["url"], the server will issue the request on the user's behalf.

I verified the behavior with the real WebFetch implementation, not with mocks. The PoC starts:

  • a loopback canary HTTP server
  • a local proxy oracle used only to observe where the tool tries to connect

Then it invokes WebFetch.execute() twice:

  1. A benign loopback URL to prove the code path works normally
  2. http://198.18.0.1:18080/special-use to prove the tool does not reject a special-use IPv4 destination

The local proxy captured the original outbound target as:

{
  "raw_target": "http://198.18.0.1:18080/special-use",
  "host": "198.18.0.1",
  "port": 18080,
  "path": "/special-use"
}

This is enough to demonstrate SSRF-style server-side network access. Full /message -> model -> tool-selection E2E was attempted previously, but the local environment stopped at the model boundary because the configured DeepSeek credential returned HTTP 401 before any tool call. That limitation does not change the underlying defect: the vulnerable sink is reachable from the agent tool plane, and web_fetch is a built-in loaded tool.

PoC

Prerequisites

  • A checkout of the repository
  • Python environment with project dependencies sufficient to import agent.tools.web_fetch.web_fetch
  • No authentication is required for the integration-level PoC
  • Note: the exploit directory named in prior notes as CVE-2026-32019-chatgpt-on-wechat-web_fetch-special-use-ipv4-exp is not present in this workspace; the validated PoC artifacts are in the sibling directory CVE-2026-32019-exp/

Reproduction Steps

  1. Download the integration PoC script from: verification_test.py
  2. Download the control script from: control-blocked-scheme.py
  3. Change into the repository root.
  4. Run the integration PoC:
    python3 verification_test.py
  5. Observe that the script prints DEFECT-CONFIRMED-WITH-LIMITATIONS and records a proxy observation whose target host is 198.18.0.1.
  6. Run the control test:
    python3 control-blocked-scheme.py
  7. Observe that the control input file:///etc/passwd is rejected with Invalid URL (must start with http:// or https://), confirming the current validation only checks the scheme and does not protect against dangerous HTTP destinations.

Log of Evidence

Integration PoC output:

Verification Mode: Integration-Test
[DEFECT-CONFIRMED-WITH-LIMITATIONS] special-use IPv4 URL reached outbound fetch sink without SSRF filtering
{
  "mode": "Integration-Test",
  "data_flow": "[Test Input URL] -> [WebFetch.execute] -> [requests.get allow_redirects=True] -> [HTTP GET to target IP] -> [canary/proxy evidence]",
  "special_use_url": "http://198.18.0.1:18080/special-use",
  "loopback_url": "http://127.0.0.1:38933/loopback",
  "proxy_url": "http://127.0.0.1:41291",
  "loopback_result": {
    "status": "success",
    "result": "Title: Untitled\\n\\nContent:\\nCVE-2026-32019-SSRF-CANARY path=/loopback"
  },
  "special_use_result": {
    "status": "success",
    "result": "Title: Untitled\\n\\nContent:\\nCVE-2026-32019-SSRF-CANARY proxied_target=http://198.18.0.1:18080/special-use"
  },
  "proxy_observation": {
    "raw_target": "http://198.18.0.1:18080/special-use",
    "host": "198.18.0.1",
    "port": 18080,
    "path": "/special-use"
  }
}

Control PoC output:

Control Mode: Integration-Test
[CONTROL-PASS] non-http(s) input was rejected by existing validation
{
  "status": "error",
  "result": "Error: Invalid URL (must start with http:// or https://)"
}

Impact

This is an SSRF issue. Any deployment that exposes agent interaction to untrusted or semi-trusted users is affected if those users can influence web_fetch tool inputs.

Practical impact includes:

  • probing internal HTTP services from the server's network position
  • reaching loopback, private, or other non-public destinations that are not accessible externally
  • retrieving content from internal web dashboards, metadata-style endpoints, or development interfaces if they trust network location rather than user authentication
  • using redirects to pivot into additional internal targets because redirects are enabled

The exact downstream impact depends on the network environment of the CowAgent host, but the primitive is real: the attacker gains server-side HTTP reachability they should not have.

Affected products

  • Ecosystem: GitHub
  • Package name: zhayujie/chatgpt-on-wechat (renamed CowAgent; Python project name cowagent)
  • Affected versions: <= 2.1.1
  • Patched versions:

Severity

  • Severity: High
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L

Weaknesses

  • CWE: CWE-918: Server-Side Request Forgery (SSRF)

Occurrences

Permalink Description
https://github.com/zhayujie/chatgpt-on-wechat/blob/8cb53e612919ddbd8231dd86e6c060222b517536/agent/tools/web_fetch/web_fetch.py#L101-L113 WebFetch.execute() accepts attacker-influenced url input and only validates that the scheme is http or https before dispatching to fetch handlers.
https://github.com/zhayujie/chatgpt-on-wechat/blob/8cb53e612919ddbd8231dd86e6c060222b517536/agent/tools/web_fetch/web_fetch.py#L117-L126 The webpage fetch path performs requests.get(..., allow_redirects=True) with no destination-IP validation, enabling SSRF to special-use or internal addresses.
https://github.com/zhayujie/chatgpt-on-wechat/blob/8cb53e612919ddbd8231dd86e6c060222b517536/agent/tools/web_fetch/web_fetch.py#L160-L167 The document download path has the same issue: it issues a streaming requests.get(..., allow_redirects=True) without validating the destination host or redirect chain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions