Skip to content

Commit 689e46d

Browse files
Merge pull request #506 from AikidoSec/never-block-bypass-ip
Never block bypass IP
2 parents 6d7aac1 + 0033b0f commit 689e46d

File tree

4 files changed

+133
-29
lines changed

4 files changed

+133
-29
lines changed

end2end/tests/hono-xml-blocklists.test.js

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,41 @@ t.beforeEach(async () => {
1414
const body = await response.json();
1515
token = body.token;
1616

17-
// Apply rate limiting
18-
const updateConfigResponse = await fetch(
19-
`${testServerUrl}/api/runtime/firewall/lists`,
20-
{
21-
method: "POST",
22-
headers: {
23-
"Content-Type": "application/json",
24-
Authorization: token,
25-
},
26-
body: JSON.stringify({
27-
blockedIPAddresses: ["1.3.2.0/24", "fe80::1234:5678:abcd:ef12/64"],
28-
blockedUserAgents: "hacker|attacker|GPTBot",
29-
}),
30-
}
31-
);
32-
t.same(updateConfigResponse.status, 200);
17+
const config = await fetch(`${testServerUrl}/api/runtime/config`, {
18+
method: "POST",
19+
headers: {
20+
"Content-Type": "application/json",
21+
Authorization: token,
22+
},
23+
body: JSON.stringify({
24+
allowedIPAddresses: ["1.3.2.1", "1.3.2.2"],
25+
endpoints: [
26+
{
27+
route: "/admin",
28+
method: "GET",
29+
forceProtectionOff: false,
30+
allowedIPAddresses: ["1.3.2.1"],
31+
rateLimiting: {
32+
enabled: false,
33+
},
34+
},
35+
],
36+
}),
37+
});
38+
t.same(config.status, 200);
39+
40+
const lists = await fetch(`${testServerUrl}/api/runtime/firewall/lists`, {
41+
method: "POST",
42+
headers: {
43+
"Content-Type": "application/json",
44+
Authorization: token,
45+
},
46+
body: JSON.stringify({
47+
blockedIPAddresses: ["1.3.2.0/24", "fe80::1234:5678:abcd:ef12/64"],
48+
blockedUserAgents: "hacker|attacker|GPTBot",
49+
}),
50+
});
51+
t.same(lists.status, 200);
3352
});
3453

3554
t.test("it blocks geo restricted IPs", (t) => {
@@ -48,7 +67,7 @@ t.test("it blocks geo restricted IPs", (t) => {
4867
});
4968

5069
server.on("error", (err) => {
51-
t.fail(err.message);
70+
t.fail(err);
5271
});
5372

5473
let stdout = "";
@@ -107,7 +126,7 @@ t.test("it blocks geo restricted IPs", (t) => {
107126
t.same(await resp3.text(), JSON.stringify({ success: true }));
108127
})
109128
.catch((error) => {
110-
t.fail(error.message);
129+
t.fail(error);
111130
})
112131
.finally(() => {
113132
server.kill();
@@ -130,7 +149,7 @@ t.test("it blocks bots", (t) => {
130149
});
131150

132151
server.on("error", (err) => {
133-
t.fail(err.message);
152+
t.fail(err);
134153
});
135154

136155
let stdout = "";
@@ -190,7 +209,73 @@ t.test("it blocks bots", (t) => {
190209
}
191210
})
192211
.catch((error) => {
193-
t.fail(error.message);
212+
t.fail(error);
213+
})
214+
.finally(() => {
215+
server.kill();
216+
});
217+
});
218+
219+
t.test("it does not block bypass IP if in blocklist", (t) => {
220+
const server = spawn(`node`, [pathToApp, "4004"], {
221+
env: {
222+
...process.env,
223+
AIKIDO_DEBUG: "true",
224+
AIKIDO_BLOCKING: "true",
225+
AIKIDO_TOKEN: token,
226+
AIKIDO_URL: testServerUrl,
227+
},
228+
});
229+
230+
server.on("close", () => {
231+
t.end();
232+
});
233+
234+
server.on("error", (err) => {
235+
t.fail(err);
236+
});
237+
238+
let stdout = "";
239+
server.stdout.on("data", (data) => {
240+
stdout += data.toString();
241+
});
242+
243+
let stderr = "";
244+
server.stderr.on("data", (data) => {
245+
stderr += data.toString();
246+
});
247+
248+
// Wait for the server to start
249+
timeout(2000)
250+
.then(async () => {
251+
const resp1 = await fetch("http://127.0.0.1:4004/", {
252+
headers: {
253+
"X-Forwarded-For": "1.3.2.1",
254+
},
255+
signal: AbortSignal.timeout(5000),
256+
});
257+
t.same(resp1.status, 200);
258+
259+
const resp2 = await fetch("http://127.0.0.1:4004/admin", {
260+
headers: {
261+
"X-Forwarded-For": "1.3.2.1",
262+
},
263+
});
264+
t.same(resp2.status, 200);
265+
266+
const resp3 = await fetch("http://127.0.0.1:4004/admin", {
267+
headers: {
268+
"X-Forwarded-For": "1.3.2.2",
269+
},
270+
});
271+
t.same(resp3.status, 403);
272+
t.same(
273+
await resp3.text(),
274+
`Your IP address is not allowed to access this resource. (Your IP: 1.3.2.2)`
275+
);
276+
})
277+
.catch((error) => {
278+
t.fail(error);
194279
})
195280
.finally(() => {
196281
server.kill();

library/sources/http-server/checkIfRequestIsBlocked.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-lines-per-function */
12
import type { ServerResponse } from "http";
23
import { Agent } from "../../agent/Agent";
34
import { getContext } from "../../agent/Context";
@@ -25,15 +26,11 @@ export function checkIfRequestIsBlocked(
2526
return false;
2627
}
2728

28-
const result = context.remoteAddress
29-
? agent.getConfig().isIPAddressBlocked(context.remoteAddress)
30-
: ({ blocked: false } as const);
31-
32-
if (result.blocked) {
29+
if (!ipAllowedToAccessRoute(context, agent)) {
3330
res.statusCode = 403;
3431
res.setHeader("Content-Type", "text/plain");
3532

36-
let message = `Your IP address is blocked due to ${escapeHTML(result.reason)}.`;
33+
let message = "Your IP address is not allowed to access this resource.";
3734
if (context.remoteAddress) {
3835
message += ` (Your IP: ${escapeHTML(context.remoteAddress)})`;
3936
}
@@ -43,11 +40,23 @@ export function checkIfRequestIsBlocked(
4340
return true;
4441
}
4542

46-
if (!ipAllowedToAccessRoute(context, agent)) {
43+
const isAllowedIP =
44+
context.remoteAddress &&
45+
agent.getConfig().isAllowedIP(context.remoteAddress);
46+
47+
if (isAllowedIP) {
48+
return false;
49+
}
50+
51+
const result = context.remoteAddress
52+
? agent.getConfig().isIPAddressBlocked(context.remoteAddress)
53+
: ({ blocked: false } as const);
54+
55+
if (result.blocked) {
4756
res.statusCode = 403;
4857
res.setHeader("Content-Type", "text/plain");
4958

50-
let message = "Your IP address is not allowed to access this resource.";
59+
let message = `Your IP address is blocked due to ${escapeHTML(result.reason)}.`;
5160
if (context.remoteAddress) {
5261
message += ` (Your IP: ${escapeHTML(context.remoteAddress)})`;
5362
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"scripts": {
99
"install": "node scripts/install.js",
1010
"install-lib-only": "node scripts/install.js --lib-only",
11-
"containers": "cd sample-apps && docker compose up -d --remove-orphans",
11+
"containers": "cd sample-apps && docker compose up -d --remove-orphans --build",
1212
"build": "node scripts/build.js",
1313
"watch": "cd library && npm run build:watch",
1414
"test": "cd library && npm run test",

sample-apps/hono-xml/app.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ async function main() {
100100
return c.json({ success: true });
101101
});
102102

103+
app.get("/admin", async (c) => {
104+
return c.html(
105+
`<html lang="en">
106+
<body>
107+
<h1>Admin panel</h1>
108+
</body>
109+
</html>`
110+
);
111+
});
112+
103113
app.post("/add-fast", async (c) => {
104114
const body = await c.req.text();
105115

0 commit comments

Comments
 (0)