Skip to content

Commit 89d6d11

Browse files
committed
IPList: Check for ipv4-mapped ipv6 addresses
1 parent df2cf03 commit 89d6d11

6 files changed

Lines changed: 61 additions & 42 deletions

File tree

agent_api/src/main/java/dev/aikido/agent_api/helpers/net/IPList.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,21 @@ public boolean matches(String ip) {
3434
}
3535
IPAddress ipAddress = ipAddressString.getAddress();
3636

37-
// Check if the IP address is in any of the blocked subnets
37+
if (containsAddress(ipAddress)) {
38+
return true;
39+
}
40+
41+
// Also try the embedded IPv4 form for IPv4-mapped IPv6 addresses (e.g. ::ffff:23.45.67.89)
42+
if (ipAddress.isIPv6() && ipAddress.toIPv6().isIPv4Convertible()) {
43+
IPAddress ipv4 = ipAddress.toIPv6().toIPv4();
44+
if (ipv4 != null && containsAddress(ipv4)) {
45+
return true;
46+
}
47+
}
48+
return false;
49+
}
50+
51+
private boolean containsAddress(IPAddress ipAddress) {
3852
for (IPAddress subnet : ipAddresses) {
3953
if (subnet.contains(ipAddress)) {
4054
return true;

agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/ssrf/IsPrivateIP.java

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
package dev.aikido.agent_api.vulnerabilities.ssrf;
22

33
import dev.aikido.agent_api.helpers.net.IPList;
4-
import inet.ipaddr.IPAddressString;
54

6-
import java.util.Arrays;
7-
import java.util.HashSet;
85
import java.util.List;
9-
import java.util.Set;
106

117
public final class IsPrivateIP {
128
// Define private IP ranges
@@ -45,10 +41,6 @@ public final class IsPrivateIP {
4541
static {
4642
PRIVATE_IP_RANGES.stream().forEach(privateIpNetworks::add);
4743
PRIVATE_IPV6_RANGES.stream().forEach(privateIpNetworks::add);
48-
// Add IPv4-mapped IPv6 addresses
49-
for (String ipv4Ranges: PRIVATE_IP_RANGES) {
50-
privateIpNetworks.add(mapIPv4ToIPv6(ipv4Ranges));
51-
}
5244
}
5345

5446
private IsPrivateIP() {
@@ -66,21 +58,4 @@ public static boolean containsPrivateIP(List<String> ipAddresses) {
6658
public static boolean isPrivateIp(String ip) {
6759
return privateIpNetworks.matches(ip);
6860
}
69-
70-
/**
71-
* Maps an IPv4 address to an IPv6 address.
72-
* e.g. 127.0.0.0/8 -> ::ffff:127.0.0.0/104
73-
*/
74-
public static String mapIPv4ToIPv6(String ip) {
75-
if (!ip.contains("/")) {
76-
// No CIDR suffix, assume /32
77-
return "::ffff:" + ip + "/128";
78-
}
79-
80-
String[] parts = ip.split("/");
81-
int suffix = Integer.parseInt(parts[1]);
82-
// We add 96 to the suffix, since ::ffff: already is 96 bits,
83-
// so the 32 remaining bits are decided by the IPv4 address
84-
return "::ffff:" + parts[0] + "/" + (suffix + 96);
85-
}
8661
}

agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/ssrf/imds/IMDSAddresses.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dev.aikido.agent_api.vulnerabilities.ssrf.imds;
22

33
import dev.aikido.agent_api.helpers.net.IPList;
4-
import static dev.aikido.agent_api.vulnerabilities.ssrf.IsPrivateIP.mapIPv4ToIPv6;
54

65
public final class IMDSAddresses {
76
private IMDSAddresses() {}
@@ -11,11 +10,9 @@ private IMDSAddresses() {}
1110
// Add the IP addresses used by AWS EC2 instances for IMDS
1211
imdsAddresses.add("169.254.169.254");
1312
imdsAddresses.add("fd00:ec2::254");
14-
imdsAddresses.add(mapIPv4ToIPv6("169.254.169.254"));
1513

1614
// Add the IP addresses used for Alibaba Cloud
1715
imdsAddresses.add("100.100.100.200");
18-
imdsAddresses.add(mapIPv4ToIPv6("100.100.100.200"));
1916
}
2017

2118
/** Checks if the IP is an IMDS IP */

agent_api/src/test/java/collectors/WebRequestCollectorTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,26 @@ void testReport_ipBlockedUsingLists_Ip_Bypassed() {
240240
assertNull(Context.get());
241241
}
242242

243+
@Test
244+
void testReport_ipBlockedUsingLists_IPv4MappedBypass() {
245+
contextObject.setIp("::ffff:192.168.1.1");
246+
247+
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(List.of(
248+
new ReportingApi.ListsResponseEntry("key", "geoip", "geoip restrictions", List.of("192.168.1.1"))
249+
), List.of(), List.of(), null, null, List.of());
250+
ServiceConfigStore.updateFromAPIListsResponse(blockedListsRes);
251+
252+
List<String> bypassedIps = List.of("192.168.1.1");
253+
ServiceConfigStore.updateFromAPIResponse(new APIResponse(
254+
true, "", getUnixTimeMS(), List.of(), List.of(), bypassedIps, false, null, true, false, List.of()
255+
));
256+
257+
WebRequestCollector.Res response = WebRequestCollector.report(contextObject);
258+
259+
assertNull(response);
260+
assertNull(Context.get());
261+
}
262+
243263
@Test
244264
void testReport_ipNotAllowedUsingLists_Ip_Bypassed() {
245265
ReportingApi.APIListsResponse blockedListsRes = new ReportingApi.APIListsResponse(

agent_api/src/test/java/helpers/IPListTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,30 @@ public void testBlocklistSubnetWithSubnet2() {
114114
assertTrue(blocklist.matches("192.168.2.1"));
115115
assertTrue(blocklist.matches("192.168.2.2"));
116116
}
117+
118+
@Test
119+
public void testBlocklistMatchesIPv4MappedIPv6() {
120+
blocklist.add("192.168.1.1");
121+
assertTrue(blocklist.matches("::ffff:192.168.1.1"));
122+
assertFalse(blocklist.matches("::ffff:192.168.1.2"));
123+
124+
blocklist.add("10.0.0.0/8");
125+
assertTrue(blocklist.matches("::ffff:10.5.6.7"));
126+
assertTrue(blocklist.matches("::ffff:10.0.0.1"));
127+
assertFalse(blocklist.matches("::ffff:11.0.0.1"));
128+
}
129+
130+
@Test
131+
public void testBlocklistIPv6OnlyIgnoresIPv4MappedMismatch() {
132+
blocklist.add("2001:db8::/32");
133+
assertTrue(blocklist.matches("2001:db8::1"));
134+
assertFalse(blocklist.matches("::ffff:192.168.1.1"));
135+
assertFalse(blocklist.matches("192.168.1.1"));
136+
}
137+
138+
@Test
139+
public void testBlocklistStoredIPv4MappedMatchesIPv4Input() {
140+
blocklist.add("::ffff:23.45.67.89");
141+
assertTrue(blocklist.matches("::ffff:23.45.67.89"));
142+
}
117143
}

agent_api/src/test/java/vulnerabilities/ssrf/IsPrivateIPTest.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,10 @@
33
import org.junit.jupiter.api.Test;
44

55
import static dev.aikido.agent_api.vulnerabilities.ssrf.IsPrivateIP.isPrivateIp;
6-
import static dev.aikido.agent_api.vulnerabilities.ssrf.IsPrivateIP.mapIPv4ToIPv6;
76
import static org.junit.jupiter.api.Assertions.*;
87

98
public class IsPrivateIPTest {
109

11-
@Test
12-
public void testMapIPv4ToIPv6() {
13-
assertEquals("::ffff:127.0.0.0/128", mapIPv4ToIPv6("127.0.0.0"));
14-
assertEquals("::ffff:127.0.0.0/104", mapIPv4ToIPv6("127.0.0.0/8"));
15-
assertEquals("::ffff:10.0.0.0/128", mapIPv4ToIPv6("10.0.0.0"));
16-
assertEquals("::ffff:10.0.0.0/104", mapIPv4ToIPv6("10.0.0.0/8"));
17-
assertEquals("::ffff:10.0.0.1/128", mapIPv4ToIPv6("10.0.0.1"));
18-
assertEquals("::ffff:10.0.0.1/104", mapIPv4ToIPv6("10.0.0.1/8"));
19-
assertEquals("::ffff:192.168.0.0/112", mapIPv4ToIPv6("192.168.0.0/16"));
20-
assertEquals("::ffff:172.16.0.0/108", mapIPv4ToIPv6("172.16.0.0/12"));
21-
}
22-
2310
@Test
2411
void testPrivateIPv4Addresses() {
2512
assertTrue(isPrivateIp("0.0.0.0"));

0 commit comments

Comments
 (0)