Add experimental C# query: SSRF host guard missing IPv6-transition unwrap (CWE-918/CWE-1389) - #21964
Open
tonghuaroot wants to merge 1 commit into
Open
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.