Skip to content

Commit c2bb181

Browse files
committed
C#: address review feedback — full RFC1918 172.16/12 coverage, accurate guard-literal comment, tighter NAT64 WKP example
1 parent 24e1d7f commit c2bb181

4 files changed

Lines changed: 28 additions & 7 deletions

File tree

csharp/src/security/CWE-918/SsrfIpv6TransitionIncompleteGuard.ql

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ predicate hasIsPrivateCall(Callable c) {
3232
}
3333

3434
/**
35-
* Holds if `c` contains a hand-written RFC 1918, loopback or cloud-metadata IPv4 literal
36-
* used as a denylist entry.
35+
* Holds if `c` contains a hand-written denylist literal: an RFC 1918 / loopback /
36+
* link-local-metadata IPv4 form (`127.0.0.1`, `169.254.169.254`, `10.`, `192.168`, the full
37+
* `172.16.0.0/12` range `172.16`-`172.31`), an IPv6 loopback or ULA fragment (`::1`, `fc00`,
38+
* `fd00`), or the cloud-metadata hostname fragment `metadata.google`.
3739
*/
3840
predicate hasRfc1918Literal(Callable c) {
3941
exists(StringLiteral s | s.getEnclosingCallable() = c |
4042
s.getValue()
41-
.regexpMatch("(?i).*(127\\.0\\.0\\.1|169\\.254\\.169\\.254|10\\.|192\\.168|172\\.1[6-9]|::1|fc00|fd00|metadata\\.google).*")
43+
.regexpMatch("(?i).*(127\\.0\\.0\\.1|169\\.254\\.169\\.254|10\\.|192\\.168|172\\.(1[6-9]|2[0-9]|3[01])|::1|fc00|fd00|metadata\\.google).*")
4244
)
4345
}
4446

csharp/src/security/CWE-918/examples/SsrfIpv6TransitionIncompleteGuardGood.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ public class GoodFetcher
1212
private static IPAddress UnwrapTransition(IPAddress addr)
1313
{
1414
byte[] b = addr.GetAddressBytes();
15-
// NAT64 well-known prefix 64:ff9b::/96 -> last 4 bytes are the embedded IPv4.
16-
if (b.Length == 16 && b[0] == 0x00 && b[1] == 0x64 && b[2] == 0xff && b[3] == 0x9b)
15+
// NAT64 well-known prefix 64:ff9b::/96 -> last 4 bytes are the embedded IPv4. The full
16+
// /96 must be matched: bytes 0..3 are 00 64 ff 9b and bytes 4..11 are all zero, so an
17+
// address that merely starts with 64:ff9b but carries non-zero middle bytes is not
18+
// mistaken for a NAT64 address and is left unwrapped.
19+
if (b.Length == 16 && b[0] == 0x00 && b[1] == 0x64 && b[2] == 0xff && b[3] == 0x9b
20+
&& b[4] == 0 && b[5] == 0 && b[6] == 0 && b[7] == 0
21+
&& b[8] == 0 && b[9] == 0 && b[10] == 0 && b[11] == 0)
1722
{
1823
return new IPAddress(new[] { b[12], b[13], b[14], b[15] });
1924
}
@@ -36,7 +41,9 @@ private static bool IsPrivateHost(string host)
3641
byte[] b = addr.GetAddressBytes();
3742
return b.Length == 4
3843
&& (b[0] == 127 || b[0] == 10 || (b[0] == 169 && b[1] == 254)
39-
|| (b[0] == 192 && b[1] == 168) || (b[0] == 172 && b[1] == 16));
44+
|| (b[0] == 192 && b[1] == 168)
45+
// Full RFC 1918 172.16.0.0/12 range: second octet 16..31.
46+
|| (b[0] == 172 && b[1] >= 16 && b[1] <= 31));
4047
}
4148

4249
public static async Task<string> FetchAsync(string host)

csharp/test/security/CWE-918/SsrfIpv6TransitionIncompleteGuard/SsrfIpv6TransitionIncompleteGuard.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ public static bool ValidateTargetHost(string host) // NOT OK
1919
return true;
2020
}
2121

22+
// BAD: a denylist covering the upper half of the RFC 1918 172.16.0.0/12 block
23+
// (172.20-172.31). The query must recognize the full /12 range, not just 172.16-172.19.
24+
public static bool CheckTargetIp(string host) // NOT OK
25+
{
26+
if (host.StartsWith("172.20") || host.StartsWith("172.31"))
27+
{
28+
throw new Exception("blocked internal host");
29+
}
30+
return true;
31+
}
32+
2233
// BAD: an `IsPrivateHost`-named guard that only does the partial `::ffff:` unwrap via
2334
// `IsIPv4MappedToIPv6` / `MapToIPv4`, leaving NAT64 and 6to4 forms live.
2435
public static bool IsPrivateHostAddress(FakeIPAddress addr) // NOT OK
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
| SsrfIpv6TransitionIncompleteGuard.cs:9:28:9:45 | ValidateTargetHost | This SSRF host guard rejects private IPv4 ranges but never unwraps IPv6-transition forms (IPv4-mapped '::ffff:', NAT64 '64:ff9b::', 6to4 '2002::'); an attacker can wrap an internal IPv4 address in a transition literal to bypass it and reach internal endpoints. |
2-
| SsrfIpv6TransitionIncompleteGuard.cs:24:28:24:47 | IsPrivateHostAddress | This SSRF host guard rejects private IPv4 ranges but never unwraps IPv6-transition forms (IPv4-mapped '::ffff:', NAT64 '64:ff9b::', 6to4 '2002::'); an attacker can wrap an internal IPv4 address in a transition literal to bypass it and reach internal endpoints. |
2+
| SsrfIpv6TransitionIncompleteGuard.cs:24:28:24:40 | CheckTargetIp | This SSRF host guard rejects private IPv4 ranges but never unwraps IPv6-transition forms (IPv4-mapped '::ffff:', NAT64 '64:ff9b::', 6to4 '2002::'); an attacker can wrap an internal IPv4 address in a transition literal to bypass it and reach internal endpoints. |
3+
| SsrfIpv6TransitionIncompleteGuard.cs:35:28:35:47 | IsPrivateHostAddress | This SSRF host guard rejects private IPv4 ranges but never unwraps IPv6-transition forms (IPv4-mapped '::ffff:', NAT64 '64:ff9b::', 6to4 '2002::'); an attacker can wrap an internal IPv4 address in a transition literal to bypass it and reach internal endpoints. |

0 commit comments

Comments
 (0)