Skip to content

Commit 37617cd

Browse files
authored
Merge pull request #827 from AikidoSec/attack-wave-samples
Collect samples for attack wave detection
2 parents a58016d + b5b0f00 commit 37617cd

File tree

11 files changed

+159
-24
lines changed

11 files changed

+159
-24
lines changed

library/agent/Agent.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as FakeTimers from "@sinonjs/fake-timers";
22
import { hostname, platform, release } from "os";
33
import * as t from "tap";
4-
import * as fetch from "../helpers/fetch";
54
import { getSemverNodeVersion } from "../helpers/getNodeVersion";
65
import { ip } from "../helpers/ipAddress";
76
import { wrap } from "../helpers/wrap";
@@ -1265,17 +1264,14 @@ t.test("attack wave detected event", async (t) => {
12651264
route: "/posts/:id",
12661265
routeParams: {},
12671266
},
1268-
metadata: {
1269-
x: "test",
1270-
},
12711267
});
12721268

12731269
t.match(api.getEvents(), [
12741270
{
12751271
type: "detected_attack_wave",
12761272
attack: {
12771273
metadata: {
1278-
x: "test",
1274+
samples: "[]",
12791275
},
12801276
},
12811277
request: {

library/agent/Agent.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -647,18 +647,27 @@ export class Agent {
647647
/**
648648
* This function gets called when an attack wave is detected, it reports this attack wave to the API
649649
*/
650-
onDetectedAttackWave({
651-
request,
652-
metadata,
653-
}: {
654-
request: Context;
655-
metadata: Record<string, string>;
656-
}) {
650+
onDetectedAttackWave({ request }: { request: Context }) {
651+
if (!request.remoteAddress) {
652+
// Cannot report attack wave without IP address
653+
// Should not happen since AttackWaveDetector checks for remoteAddress
654+
return;
655+
}
656+
657+
const samples = this.attackWaveDetector.getSamplesForIP(
658+
request.remoteAddress
659+
);
660+
657661
const attack: DetectedAttackWave = {
658662
type: "detected_attack_wave",
659663
time: Date.now(),
660664
attack: {
661-
metadata: limitLengthMetadata(metadata, 4096),
665+
metadata: limitLengthMetadata(
666+
{
667+
samples: JSON.stringify(samples),
668+
},
669+
4096
670+
),
662671
user: request.user,
663672
},
664673
request: {

library/agent/api/Event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ type Heartbeat = {
158158
export type DetectedAttackWave = {
159159
type: "detected_attack_wave";
160160
request: {
161-
ipAddress: string | undefined;
161+
ipAddress: string;
162162
userAgent: string | undefined;
163163
source: string;
164164
};

library/agent/api/ReportingAPIRateLimitedClientSide.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function generateAttackWaveEvent(): Event {
6262
type: "detected_attack_wave",
6363
time: Date.now(),
6464
request: {
65-
ipAddress: undefined,
65+
ipAddress: "::1",
6666
userAgent: undefined,
6767
source: "express",
6868
},

library/sources/FunctionsFramework.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ t.test("it waits for attack events to be sent before returning", async (t) => {
299299

300300
agent.onDetectedAttackWave({
301301
request: getContext()!,
302-
metadata: {},
303302
});
304303

305304
res.sendStatus(200);

library/sources/HTTPServer.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,34 @@ t.test("it reports attack waves", async (t) => {
11031103
{
11041104
type: "detected_attack_wave",
11051105
attack: {
1106-
metadata: {},
1106+
metadata: {
1107+
samples: JSON.stringify([
1108+
{
1109+
method: "GET",
1110+
url: "/../package.json",
1111+
},
1112+
{
1113+
method: "GET",
1114+
url: "/.env",
1115+
},
1116+
{
1117+
method: "GET",
1118+
url: "/wp-config.php",
1119+
},
1120+
{
1121+
method: "GET",
1122+
url: "/etc/passwd",
1123+
},
1124+
{
1125+
method: "GET",
1126+
url: "/.git/config",
1127+
},
1128+
{
1129+
method: "GET",
1130+
url: "/%systemroot%/system32/cmd.exe",
1131+
},
1132+
]),
1133+
},
11071134
user: undefined,
11081135
},
11091136
request: {

library/sources/Lambda.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,6 @@ t.test("it waits for attack events to be sent before returning", async (t) => {
558558

559559
agent.onDetectedAttackWave({
560560
request: getContext()!,
561-
metadata: {},
562561
});
563562

564563
return { statusCode: 200 };

library/sources/http-server/createRequestListener.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ function onFinishRequestHandler(
118118
!agent.getConfig().isBypassedIP(context.remoteAddress) &&
119119
agent.getAttackWaveDetector().check(context)
120120
) {
121-
agent.onDetectedAttackWave({ request: context, metadata: {} });
121+
agent.onDetectedAttackWave({
122+
request: context,
123+
});
122124
agent.getInspectionStatistics().onAttackWaveDetected();
123125
}
124126
}

library/sources/http-server/http2/createStreamListener.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ function discoverRouteFromStream(
8181
!agent.getConfig().isBypassedIP(context.remoteAddress) &&
8282
agent.getAttackWaveDetector().check(context)
8383
) {
84-
agent.onDetectedAttackWave({ request: context, metadata: {} });
84+
agent.onDetectedAttackWave({
85+
request: context,
86+
});
8587
agent.getInspectionStatistics().onAttackWaveDetected();
8688
}
8789
}

library/vulnerabilities/attack-wave-detection/AttackWaveDetector.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function newAttackWaveDetector() {
3030
attackWaveTimeFrame: 60 * 1000,
3131
minTimeBetweenEvents: 60 * 60 * 1000,
3232
maxLRUEntries: 10_000,
33+
maxSamplesPerIP: 5,
3334
});
3435
}
3536

@@ -150,3 +151,55 @@ t.test("a slow web scanner that triggers in the third interval", async (t) => {
150151

151152
clock.uninstall();
152153
});
154+
155+
t.test("it collects samples correctly", async (t) => {
156+
const detector = newAttackWaveDetector();
157+
const ip = "::1";
158+
detector.check(getTestContext(ip, "/wp-config.php", "GET"));
159+
detector.check(getTestContext(ip, "/wp-config.php.bak", "GET"));
160+
detector.check(getTestContext(ip, "/.git/config", "GET"));
161+
detector.check(getTestContext(ip, "/.env", "GET"));
162+
detector.check(getTestContext(ip, "/.htaccess", "GET"));
163+
164+
detector.check(getTestContext(ip, "/.htaccess", "GET")); // Duplicate
165+
detector.check(getTestContext("::2", "/test/.env", "GET")); // Different IP
166+
167+
const samples = detector.getSamplesForIP(ip);
168+
t.equal(samples.length, 5, "should have collected 5 samples");
169+
170+
t.same(
171+
samples,
172+
[
173+
{ method: "GET", url: "http://localhost:4000/wp-config.php" },
174+
{ method: "GET", url: "http://localhost:4000/wp-config.php.bak" },
175+
{ method: "GET", url: "http://localhost:4000/.git/config" },
176+
{ method: "GET", url: "http://localhost:4000/.env" },
177+
{ method: "GET", url: "http://localhost:4000/.htaccess" },
178+
],
179+
"should have collected the correct samples"
180+
);
181+
});
182+
183+
t.test("it limits samples correctly", async (t) => {
184+
const detector = newAttackWaveDetector();
185+
const ip = "::1";
186+
187+
for (let i = 0; i < 10; i++) {
188+
detector.check(getTestContext(ip, `/${i}/.env`, "GET"));
189+
}
190+
191+
const samples = detector.getSamplesForIP(ip);
192+
t.equal(samples.length, 5, "should have collected maximum 5 samples");
193+
194+
t.same(
195+
samples,
196+
[
197+
{ method: "GET", url: "http://localhost:4000/0/.env" },
198+
{ method: "GET", url: "http://localhost:4000/1/.env" },
199+
{ method: "GET", url: "http://localhost:4000/2/.env" },
200+
{ method: "GET", url: "http://localhost:4000/3/.env" },
201+
{ method: "GET", url: "http://localhost:4000/4/.env" },
202+
],
203+
"should have collected the correct samples"
204+
);
205+
});

0 commit comments

Comments
 (0)