Skip to content

Add experimental C# query: SSRF host guard missing IPv6-transition unwrap (CWE-918/CWE-1389) - #21964

Open
tonghuaroot wants to merge 1 commit into
github:mainfrom
tonghuaroot:experimental-ssrf-ipv6-transition-csharp
Open

Add experimental C# query: SSRF host guard missing IPv6-transition unwrap (CWE-918/CWE-1389)#21964
tonghuaroot wants to merge 1 commit into
github:mainfrom
tonghuaroot:experimental-ssrf-ipv6-transition-csharp

Conversation

@tonghuaroot

Copy link
Copy Markdown
Contributor

What

Adds a new experimental C# query, cs/ssrf-ipv6-transition-incomplete-guard, plus a .qhelp, bad/good examples, a change note, and a test pack. It is the C# counterpart to the JavaScript query in #21950.

The query flags a hand-rolled SSRF host guard that rejects private/loopback IPv4 ranges but never unwraps IPv6-transition forms, so the guard can be bypassed by wrapping an internal IPv4 address in a transition literal.

Motivation

A common SSRF defense rejects the request host against a denylist of private, loopback and cloud-metadata IPv4 ranges. When the guard inspects only the dotted-quad IPv4 form and never normalizes IPv6-transition representations, an attacker can wrap an internal address in a transition literal:

  • IPv4-mapped — ::ffff:169.254.169.254
  • NAT64 — 64:ff9b::a9fe:a9fe
  • 6to4 — 2002::

new Uri("http://[::ffff:169.254.169.254]/") parses the host, but a dotted-quad denylist never sees the embedded IPv4 and classifies the address as public, while the OS still routes to the internal endpoint.

The .NET-specific footgun is a partial unwrap: IPAddress.MapToIPv4() / IsIPv4MappedToIPv6 only canonicalizes the IPv4-mapped ::ffff:0:0/96 block, so a guard that maps-then-checks still lets NAT64 (64:ff9b::/96), 6to4 (2002::/16) and IPv4-compatible forms through. The query treats that partial unwrap as an unsafe signal.

Why this is a separate, experimental query

The existing CWE-918 query cs/request-forgery is pure taint-flow: source = remote-flow source, sink = outbound request URL. It has no notion of a host-validation guard, so there is no notion of that guard being incomplete for IPv6-transition addresses. A target with a hand-rolled dotted-quad denylist is treated (correctly) as out of scope by the taint-flow query; this query covers the orthogonal guard-completeness question. It is a standalone @kind problem query and does not touch any supported query.

Metadata follows the experimental rules and mirrors #21950: @tags includes experimental, security, external/cwe/cwe-918, external/cwe/cwe-1389; @security-severity and @precision are omitted (staff-assigned). Matching is on getName()/getValue() only — no toString regexp matching, no getAQlClass, no internal libraries.

Tests

csharp/ql/test/experimental/CWE-918/SsrfIpv6TransitionIncompleteGuard/:

  • ValidateTargetHost — RFC1918/loopback denylist, no transition unwrap — flagged (true positive)
  • IsPrivateHostAddress — partial MapToIPv4() / ::ffff:-only unwrap — flagged (true positive)
  • explicit 64:ff9b/2002:/::ffff: literal handled in code — not flagged
  • extract-embedded-IPv4 helper by name — not flagged
  • non-validator-named callable carrying the same signals — not flagged

codeql test run passes (2 bad guards flagged, 3 safe/out-of-scope suppressed) and the query compiles with no errors or warnings against the current csharp-all pack. not_included_in_qls.expected is updated in codepoint-sorted order.

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment