Repository navigation
[Security]SSRF protection bypass via HTTP proxy routing in DeepSeek Harness web_fetch (dsh v0.2.1-alpha.1) #9680
Replies: 2 comments
|
你的代码引用逐字准确,这一点我核过了。但定性不成立:这是有文档的有意设计,而且设计笔记自己就写明"代理被信任做目标选择"。建议把诉求从"绕过漏洞"改成"请复核这条边界是否仍然要维持"。 1. ✅ 你引的代码我都核了(
|
Uh oh!
There was an error while loading. Please reload this page.
1. Product Introduction
DeepSeek Harness (
dsh) is an open-source AI-agent harness developed by DeepSeek AI. Built on an "everything-is-a-plugin" Cordis architecture, it executes model-generated code and commands, loads third-party plugins, and accesses the network, processes, credentials, and files. It launches through named profiles (web,headless,sdk,acp); thewebprofile enables the model-facingweb_searchandweb_fetchtools by default. This vulnerability resides in theweb_fetchHTTP provider (@deepseek-ai/dsh-web-fetch-http) and the outbound proxy routing library (@deepseek-ai/dsh-http-proxy).2. Vulnerability Title
SSRF protection bypass via HTTP proxy routing in DeepSeek Harness
web_fetch(dsh v0.2.1-alpha.1)3. Summary / Description
DeepSeek Harness v0.2.1-alpha.1 is affected by a Server-Side Request Forgery (SSRF) protection bypass in the
web_fetchtool. When the deployment has an HTTP(S) proxy configured (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY), outbound requests take a proxied branch that skips the destination-IP validation (resolvePublicAddresses) entirely. An attacker who controls content the agent reads (indirect prompt injection) can therefore make the agent fetch a hostname that resolves to a private/loopback/link-local/cloud-metadata address — e.g.http://169.254.169.254.nip.io/orhttp://metadata.internal/— and the proxy will resolve and connect to that internal target, leaking cloud-metadata credentials or probing internal services. No authentication is required; the impact is on confidentiality.4. Technical Analysis / Root Cause
Weakness: CWE-918 — Server-Side Request Forgery (SSRF)
Source-to-sink chain:
Code location:
File: packages/web/web-fetch-http/src/provider.ts Line: 134-139 Relevant code: const route = proxyRouteFor(url) if (route.proxied && !isNonPublicIpLiteral(url.hostname)) { return await publicHttpNetwork.requestVia(route.dispatcher, url, headers, signal) // no IP check } const addresses = await this.resolveAddresses(url.hostname, signal) // SSRF guard return await publicHttpNetwork.request(url, addresses, headers, signal)File: packages/web/web-fetch-http/src/network.ts Line: 171-174 Relevant code (the only guard on the proxied branch; literal IPs only): export function isNonPublicIpLiteral(hostname: string): boolean { const unbracketed = stripIpv6Brackets(hostname) return isIP(unbracketed) !== 0 && !isPublicIpAddress(unbracketed) }File: packages/util/http-proxy/src/policy.ts Line: 338-343 Relevant code (routing decision compares the hostname STRING, never resolves it): export function proxyForUrl(policy, url) { const proxy = url.protocol === 'https:' ? policy.httpsProxy : ... policy.httpProxy : undefined if (proxy === undefined) return undefined if (isLoopbackHost(url.hostname)) return undefined return bypassesProxy(policy.noProxy, url) ? undefined : proxy }Validity check:
web_fetchis enabled by default (packages/bundle/base/cordis.patch.yml:476,489).urlis a model tool argument, driven by indirect prompt injection.isNonPublicIpLiteralonly rejects IP literals; hostnames (*.nip.io, internal DNS names, DNS-rebinding domains) bypass it.AccessKeyId/SecretAccessKey/Tokenfrom a fake metadata service.Root cause:
requestOnce()conflates "is this request proxied" with "can we skip destination validation". The proxy branch skipsresolveAddresseson the assumption that there is no locally-resolvable address (DNS happens proxy-side) — but failing to validate is exactly what lets an attacker hand over a hostname that resolves to an internal address. The only guard on that branch,isNonPublicIpLiteral(), usesnet.isIP()and therefore can never classify a hostname.5. Affected Versions
web_fetchpath.git describe --tags=dsh-v0.2.1-alpha.1(the@deepseek-ai/dsh-web-fetch-httppackagepackage.jsonversion =0.2.1-alpha.1);@deepseek-ai/dsh@0.2.1-alpha.1(with@deepseek-ai/dsh-web-fetch-http@0.2.1-alpha.1), booted as a live Web target athttp://127.0.0.1:3080and confirmed reachable.enginesrequires^22.19 || >=24), Windows 11; the server-side reproduction uses only built-innode:http/node:net(the POC has no third-party dependencies).6. Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:NCVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:N/SA:N7. CVSS Vector
8. Reproduction Steps
8.1 Environment
git describe --tags=dsh-v0.2.1-alpha.1@deepseek-ai/dsh@0.2.1-alpha.1(live Web target athttp://127.0.0.1:3080)node:http/node:net)127.0.0.1:8080)8.2 Steps
Step 1 — Set up the environment
Run the bundled POC. It starts a fake "instance metadata" HTTP service on 127.0.0.1:18080 returning example
AccessKeyId/SecretAccessKey/Token, then a minimal forward proxy on 127.0.0.1:18081 whose resolver maps*.internalhostnames to 127.0.0.1 (mimicking a real proxy's resolver).Step 2 — Show the routing decision
POC Section A prints each URL's decision under an active proxy:
Step 3 — Trigger and observe (end-to-end)
POC Section B requests
http://metadata.internal:18080/latest/meta-data/...through the proxy; the proxy resolves it to 127.0.0.1 and hits the metadata service:Step 4 — Verify impact
In a real deployment, replace the hostname with
http://169.254.169.254.nip.io/latest/meta-data/iam/security-credentials/(AWS),http://metadata.google.internal/...(GCP), or an internal hostname to read cloud-metadata credentials or probe internal services; the response is returned into the agent context viaweb_fetch, ready for the attacker to induce exfiltration.9. Proof of Concept
9.1 Format selection
9.2 Usage
node poc/poc-ssrf-proxy.js9.3 POC Code
See
poc/poc-ssrf-proxy.jsin this directory (Section A decision table + Section B end-to-end demo; self-hosted on 127.0.0.1, read-only, non-weaponized).9.4 Expected output / success criteria
Section A shows hostnames mapped to
PROXIED (resolveAddresses skipped); Section B shows the proxy returningAccessKeyId/SecretAccessKey/Tokenfrom the fake metadata service.10. Mitigation / Fix Recommendations
10.1 Temporary mitigations
web_fetch(tool-web.fetch: false) in environments that process untrusted content, or remove/tighten the outbound proxy.NO_PROXY(including*.nip.io,metadata.*) so they take the direct path and are caught by the SSRF guard. Note: list-based filtering is leaky and is only a stopgap.10.2 Root-cause fixes
resolvePublicAddressesand reject private/loopback/link-local/metadata addresses, or at minimum block169.254.169.254, private-range hostnames, and*.nip.io/DNS-rebinding domains before routing to the proxy.10.3 Verification
Re-run the POC: with a proxy configured,
web_fetchagainstmetadata.internal/169.254.169.254.nip.ioshould returnWEB_BLOCKED_URL(or equivalent), and the proxy should receive no corresponding outbound connection.11. False-Positive Elimination / Impact Justification
Appendix A: Timeline / References
Appendix B: Evidence Checklist
audit-findings.md, with file:line and key code)poc/poc-ssrf-proxy.js, decision table + end-to-end output)evidence/per §8)All reactions