Skip to content

Commit efb662a

Browse files
Merge pull request #246 from AikidoSec/attack-reporting-ignore-context-if-not-set
Attack event creation: Still send api event even without a context
2 parents 99e33f9 + 6fa50aa commit efb662a

3 files changed

Lines changed: 154 additions & 12 deletions

File tree

agent_api/src/main/java/dev/aikido/agent_api/background/cloud/api/events/DetectedAttack.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import dev.aikido.agent_api.context.User;
66
import dev.aikido.agent_api.vulnerabilities.Attack;
77

8-
import java.util.List;
98
import java.util.Map;
109

1110
import static dev.aikido.agent_api.background.cloud.GetManagerInfo.getManagerInfo;
@@ -46,24 +45,41 @@ public record AttackData (
4645

4746
public static DetectedAttackEvent createAPIEvent(Attack attack, ContextObject context) {
4847
boolean blocking = getConfig().isBlockingEnabled();
49-
RequestData requestData = new RequestData(
48+
return new DetectedAttackEvent(
49+
"detected_attack", // type
50+
buildRequestData(context), // request
51+
buildAttackData(attack, blocking), // attack
52+
getManagerInfo(), // agent
53+
getUnixTimeMS() // time
54+
);
55+
}
56+
57+
private static RequestData buildRequestData(ContextObject context) {
58+
if (context == null) {
59+
return null;
60+
}
61+
return new RequestData(
5062
context.getMethod(),
5163
context.getRemoteAddress(),
5264
context.getHeader("user-agent"),
5365
context.getUrl(),
5466
context.getSource(),
5567
context.getRoute()
5668
);
57-
AttackData attackData = new AttackData(
58-
attack.kind, attack.operation, attack.source, attack.pathToPayload, attack.payload, attack.metadata,
59-
"module", blocking, attack.stack, attack.user
60-
);
61-
return new DetectedAttackEvent(
62-
"detected_attack", // type
63-
requestData, // request
64-
attackData, // attack
65-
getManagerInfo(), // agent
66-
getUnixTimeMS() // time
69+
}
70+
71+
private static AttackData buildAttackData(Attack attack, boolean blocking) {
72+
return new AttackData(
73+
attack.kind,
74+
attack.operation,
75+
attack.source,
76+
attack.pathToPayload,
77+
attack.payload,
78+
attack.metadata,
79+
"module",
80+
blocking,
81+
attack.stack,
82+
attack.user
6783
);
6884
}
6985
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package background.cloud.api;
2+
3+
import dev.aikido.agent_api.background.cloud.api.events.DetectedAttack;
4+
import dev.aikido.agent_api.context.ContextObject;
5+
import dev.aikido.agent_api.vulnerabilities.Attack;
6+
import dev.aikido.agent_api.vulnerabilities.Vulnerabilities;
7+
import org.junit.jupiter.api.Test;
8+
import utils.EmptySampleContextObject;
9+
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
class DetectedAttackTest {
16+
17+
@Test
18+
void createAPIEvent_WithValidContextAndAttack_ReturnsDetectedAttackEvent() {
19+
// Arrange
20+
ContextObject context = new EmptySampleContextObject("test", "/api/resource", "POST");
21+
22+
Map<String, String> metadata = new HashMap<>();
23+
metadata.put("key", "value");
24+
25+
Attack attack = new Attack(
26+
"read",
27+
new Vulnerabilities.SQLInjectionVulnerability(),
28+
"web",
29+
"/api/resource",
30+
metadata,
31+
"test_payload",
32+
"stack_trace",
33+
null
34+
);
35+
36+
// Act
37+
DetectedAttack.DetectedAttackEvent event = DetectedAttack.createAPIEvent(attack, context);
38+
39+
// Assert
40+
assertNotNull(event);
41+
assertEquals("detected_attack", event.type());
42+
assertNotNull(event.request());
43+
assertEquals("POST", event.request().method());
44+
assertEquals("web", event.request().source());
45+
assertEquals("/api/resource", event.request().route());
46+
assertNotNull(event.attack());
47+
assertEquals("sql_injection", event.attack().kind());
48+
assertEquals("read", event.attack().operation());
49+
assertEquals("web", event.attack().source());
50+
assertEquals("/api/resource", event.attack().path());
51+
assertEquals("test_payload", event.attack().payload());
52+
assertEquals(metadata, event.attack().metadata());
53+
assertEquals("module", event.attack().module());
54+
assertEquals("stack_trace", event.attack().stack());
55+
assertNull(event.attack().user());
56+
assertNotNull(event.agent());
57+
assertTrue(event.time() > 0);
58+
}
59+
60+
@Test
61+
void createAPIEvent_WithNullContext_ReturnsDetectedAttackEventWithNullRequest() {
62+
// Arrange
63+
Attack attack = new Attack(
64+
"read",
65+
new Vulnerabilities.SQLInjectionVulnerability(),
66+
"web",
67+
"/api/resource",
68+
Map.of("key", "value"),
69+
"test_payload",
70+
"stack_trace",
71+
null
72+
);
73+
74+
// Act
75+
DetectedAttack.DetectedAttackEvent event = DetectedAttack.createAPIEvent(attack, null);
76+
77+
// Assert
78+
assertNotNull(event);
79+
assertEquals("detected_attack", event.type());
80+
assertNull(event.request());
81+
assertNotNull(event.attack());
82+
assertEquals("sql_injection", event.attack().kind());
83+
assertEquals("read", event.attack().operation());
84+
assertEquals("web", event.attack().source());
85+
assertEquals("/api/resource", event.attack().path());
86+
assertEquals("test_payload", event.attack().payload());
87+
assertEquals(Map.of("key", "value"), event.attack().metadata());
88+
assertEquals("module", event.attack().module());
89+
assertEquals("stack_trace", event.attack().stack());
90+
assertNull(event.attack().user());
91+
assertNotNull(event.agent());
92+
assertTrue(event.time() > 0);
93+
}
94+
}

agent_api/src/test/java/collectors/DNSRecordCollectorTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package collectors;
22

3+
import dev.aikido.agent_api.background.cloud.api.events.DetectedAttack;
34
import dev.aikido.agent_api.collectors.DNSRecordCollector;
45
import dev.aikido.agent_api.context.Context;
56
import dev.aikido.agent_api.context.ContextObject;
7+
import dev.aikido.agent_api.storage.AttackQueue;
68
import dev.aikido.agent_api.storage.Hostnames;
79
import dev.aikido.agent_api.storage.HostnamesStore;
810
import dev.aikido.agent_api.storage.ServiceConfigStore;
11+
import dev.aikido.agent_api.vulnerabilities.Attack;
912
import dev.aikido.agent_api.vulnerabilities.ssrf.SSRFException;
1013
import dev.aikido.agent_api.vulnerabilities.ssrf.StoredSSRFException;
1114
import org.junit.jupiter.api.*;
@@ -29,12 +32,14 @@ void setup() throws UnknownHostException {
2932
inetAddress1 = InetAddress.getByName("1.1.1.1");
3033
inetAddress2 = InetAddress.getByName("127.0.0.1");
3134
imdsAddress1 = InetAddress.getByName("169.254.169.254");
35+
AttackQueue.clear();
3236
}
3337

3438
@AfterEach
3539
public void cleanup() {
3640
HostnamesStore.clear();
3741
Context.set(null);
42+
AttackQueue.clear();
3843
}
3944

4045
@Test
@@ -129,4 +134,31 @@ public void testHostnameSameWithContextAsAStoredSSRFAttack() {
129134
});
130135
});
131136
}
137+
138+
@Test
139+
public void testStoredSSRFWithNoContext() throws InterruptedException {
140+
ServiceConfigStore.updateBlocking(true);
141+
142+
Context.set(null);
143+
144+
Exception exception = assertThrows(StoredSSRFException.class, () -> {
145+
DNSRecordCollector.report("dev.aikido", new InetAddress[]{
146+
imdsAddress1, inetAddress2
147+
});
148+
});
149+
DetectedAttack.DetectedAttackEvent event = (DetectedAttack.DetectedAttackEvent) AttackQueue.get();
150+
assertEquals("stored_ssrf", event.attack().kind());
151+
assertNull(event.request());
152+
153+
assertEquals("Aikido Zen has blocked a stored server-side request forgery", exception.getMessage());
154+
155+
assertDoesNotThrow(() -> {
156+
DNSRecordCollector.report("metadata.goog", new InetAddress[]{
157+
imdsAddress1, inetAddress2
158+
});
159+
DNSRecordCollector.report("metadata.google.internal", new InetAddress[]{
160+
imdsAddress1, inetAddress2
161+
});
162+
});
163+
}
132164
}

0 commit comments

Comments
 (0)