Add experimental C# query: SSRF host guard missing IPv6-transition unwrap (CWE-918/CWE-1389)#21964
Conversation
…wrap (CWE-918/CWE-1389) Mirrors the JavaScript experimental query SsrfIpv6TransitionIncompleteGuard. Flags SSRF host-validation guards that reject private/loopback IPv4 ranges but never unwrap IPv6-transition forms (IPv4-mapped ::ffff:, NAT64 64:ff9b::, 6to4 2002::), so an internal IPv4 address wrapped in a transition literal bypasses the guard. A partial MapToIPv4 / IsIPv4MappedToIPv6 unwrap (which only canonicalizes ::ffff:0:0/96) is treated as an unsafe signal; an explicit transition-prefix literal or extract-embedded-IPv4 helper suppresses the alert. Signed-off-by: tonghuaroot <23011166+tonghuaroot@users.noreply.github.com>
|
Hi @tonghuaroot . Thank you very much for looking into making such a query for C#. We are currently in the process of moving/migrating all experimental queries to the Community Packs repo, which can be found here. Perhaps, you could open a PR with the query in the community packs repo instead? |
|
Thanks @michaelnebel — that makes sense, happy to do that. I'll port the query and its tests over to the Community Packs repo and open a PR there, then link it back here. Feel free to close this one, or I'll close it once the Community Packs PR lands — whichever you prefer. |
|
Done — opened it in the Community Packs repo: GitHubSecurityLab/CodeQL-Community-Packs#148 (compiles clean and the query test passes there). Thanks again for the pointer! Happy to close this PR whenever suits you. |
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:
::ffff:169.254.169.25464:ff9b::a9fe:a9fe2002::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()/IsIPv4MappedToIPv6only canonicalizes the IPv4-mapped::ffff:0:0/96block, 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-forgeryis 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 problemquery and does not touch any supported query.Metadata follows the experimental rules and mirrors #21950:
@tagsincludesexperimental,security,external/cwe/cwe-918,external/cwe/cwe-1389;@security-severityand@precisionare omitted (staff-assigned). Matching is ongetName()/getValue()only — notoStringregexp matching, nogetAQlClass, no internal libraries.Tests
csharp/ql/test/experimental/CWE-918/SsrfIpv6TransitionIncompleteGuard/:ValidateTargetHost— RFC1918/loopback denylist, no transition unwrap — flagged (true positive)IsPrivateHostAddress— partialMapToIPv4()/::ffff:-only unwrap — flagged (true positive)64:ff9b/2002:/::ffff:literal handled in code — not flaggedcodeql test runpasses (2 bad guards flagged, 3 safe/out-of-scope suppressed) and the query compiles with no errors or warnings against the currentcsharp-allpack.not_included_in_qls.expectedis updated in codepoint-sorted order.