Skip to content

Commit b7f8fe5

Browse files
committed
Report IMDS attacks
1 parent c741191 commit b7f8fe5

4 files changed

Lines changed: 62 additions & 16 deletions

File tree

agent_api/src/main/java/dev/aikido/agent_api/vulnerabilities/Vulnerabilities.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ public String getKind() {
3939
return "ssrf";
4040
}
4141
}
42+
public static final class StoredSSRFVulnerability implements Vulnerability {
43+
@Override
44+
public Detector getDetector() { return null; }
45+
@Override
46+
public String getKind() {
47+
return "stored-ssrf";
48+
}
49+
}
4250
public static final class ShellInjectionVulnerability implements Vulnerability {
4351
@Override
4452
public Detector getDetector() { return new ShellInjectionDetector(); }

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

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,50 @@ public Attack run(String hostname, int port, List<String> ipAddresses, String op
2323
return null;
2424
}
2525

26-
if (resolvesToImdsIp(new HashSet<>(ipAddresses), hostname)) {
27-
// An attacker could have stored a hostname in a database that points to an IMDS IP address
28-
// We don't check if the user input contains the hostname because context might not be available
26+
String imdsIp = resolvesToImdsIp(new HashSet<>(ipAddresses), hostname);
27+
if (imdsIp != null) {
28+
// Check if hostname is in user input (context available)
29+
ContextObject context = Context.get();
30+
if (context != null) {
31+
FindHostnameInContext.Res attackFindings = findHostnameInContext(hostname, context, port);
32+
if (attackFindings != null) {
33+
// Regular SSRF - hostname found in user input
34+
return new Attack(
35+
operation,
36+
new Vulnerabilities.SSRFVulnerability(),
37+
attackFindings.source(),
38+
attackFindings.pathToPayload(),
39+
Map.of(
40+
"hostname", hostname,
41+
"privateIP", imdsIp
42+
),
43+
attackFindings.payload(),
44+
getCurrentStackTrace(),
45+
context.getUser()
46+
);
47+
}
48+
}
49+
50+
// Stored SSRF - no context or hostname not in user input
51+
Attack storedSsrfAttack = new Attack(
52+
operation,
53+
new Vulnerabilities.StoredSSRFVulnerability(),
54+
null, // source is null for stored attacks
55+
"", // path is empty
56+
Map.of(
57+
"hostname", hostname,
58+
"privateIP", imdsIp
59+
),
60+
hostname, // payload is the hostname
61+
getCurrentStackTrace(),
62+
null // user is null for stored attacks
63+
);
64+
2965
if(shouldBlock()) {
3066
throw SSRFException.get();
3167
}
68+
69+
return storedSsrfAttack;
3270
}
3371
if (!containsPrivateIP(ipAddresses)) {
3472
// No real danger, returning.

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
public final class Resolver {
66
private Resolver() {}
77
/**
8-
* Returns boolean value that's true if this is an IMDS IP/Hostname
8+
* Returns the IMDS IP if found, otherwise null
99
*/
10-
public static boolean resolvesToImdsIp(Set<String> resolvedIpAddresses, String hostname) {
10+
public static String resolvesToImdsIp(Set<String> resolvedIpAddresses, String hostname) {
1111
// Allow access to Google Cloud metadata service as you need to set specific headers to access it
1212
// We don't want to block legitimate requests
1313
if (TrustedHosts.isTrustedHostname(hostname)) {
14-
return false;
14+
return null;
1515
}
1616
for (String ip : resolvedIpAddresses) {
1717
if (IMDSAddresses.isImdsIpAddress(ip)) {
18-
return true;
18+
return ip;
1919
}
2020
}
21-
return false;
21+
return null;
2222
}
2323
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@
77
import java.util.Set;
88

99
import static dev.aikido.agent_api.vulnerabilities.ssrf.imds.Resolver.resolvesToImdsIp;
10-
import static org.junit.jupiter.api.Assertions.assertFalse;
11-
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertNull;
1212

1313
public class ResolverTest {
1414
@Test
1515
void testResolvesToImdsIp_WithTrustedHostname() {
1616
Set<String> resolvedIps = new HashSet<>();
1717
resolvedIps.add("192.168.1.1"); // Non-IMDS IP
1818

19-
assertFalse(Resolver.resolvesToImdsIp(resolvedIps, "metadata.google.internal"));
19+
assertNull(Resolver.resolvesToImdsIp(resolvedIps, "metadata.google.internal"));
2020
}
2121

2222
@Test
2323
void testResolvesToImdsIp_WithImdsIp() {
2424
Set<String> resolvedIps = new HashSet<>();
2525
resolvedIps.add("169.254.169.254"); // IMDS IP
2626

27-
assertTrue(Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
27+
assertEquals("169.254.169.254", Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
2828
}
2929

3030
@Test
@@ -33,7 +33,7 @@ void testResolvesToImdsIp_WithMultipleResolvedIps_OneImdsIp() {
3333
resolvedIps.add("192.168.1.1"); // Non-IMDS IP
3434
resolvedIps.add("fd00:ec2::254"); // IMDS IP
3535

36-
assertTrue(Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
36+
assertEquals("fd00:ec2::254", Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
3737
}
3838

3939
@Test
@@ -42,7 +42,7 @@ void testResolvesToImdsIp_WithNoImdsIp() {
4242
resolvedIps.add("192.168.1.1"); // Non-IMDS IP
4343
resolvedIps.add("10.0.0.1"); // Another Non-IMDS IP
4444

45-
assertFalse(Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
45+
assertNull(Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
4646
}
4747

4848
@Test
@@ -51,14 +51,14 @@ void testResolvesToImdsIp_WithMultipleResolvedIps_NoImdsIp() {
5151
resolvedIps.add("192.168.1.1"); // Non-IMDS IP
5252
resolvedIps.add("10.0.0.1"); // Another Non-IMDS IP
5353

54-
assertFalse(Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
54+
assertNull(Resolver.resolvesToImdsIp(resolvedIps, "example.com"));
5555
}
5656

5757
@Test
5858
void testResolvesToImdsIp_WithMultipleResolvedIps_OnlyTrustedHostname() {
5959
Set<String> resolvedIps = new HashSet<>();
6060
resolvedIps.add("169.254.169.254"); // IMDS IP
6161

62-
assertFalse(Resolver.resolvesToImdsIp(resolvedIps, "metadata.google.internal"));
62+
assertNull(Resolver.resolvesToImdsIp(resolvedIps, "metadata.google.internal"));
6363
}
6464
}

0 commit comments

Comments
 (0)