Skip to content

Commit 896a9bc

Browse files
authored
Merge pull request #554 from AikidoSec/e2e-code-injection
Add end2end test for code injection using `new Function(...)`
2 parents 768162e + d667cef commit 896a9bc

File tree

3 files changed

+130
-6
lines changed

3 files changed

+130
-6
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const t = require("tap");
2+
const { spawn } = require("child_process");
3+
const { resolve } = require("path");
4+
const timeout = require("../timeout");
5+
6+
const pathToApp = resolve(
7+
__dirname,
8+
"../../sample-apps/express-mongodb",
9+
"app.js"
10+
);
11+
12+
t.setTimeout(60000);
13+
14+
t.test("it blocks in blocking mode", (t) => {
15+
const server = spawn(`node`, [pathToApp, "4000"], {
16+
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCK: "true" },
17+
});
18+
19+
server.on("close", () => {
20+
t.end();
21+
});
22+
23+
server.on("error", (err) => {
24+
t.fail(err);
25+
});
26+
27+
let stdout = "";
28+
server.stdout.on("data", (data) => {
29+
stdout += data.toString();
30+
});
31+
32+
let stderr = "";
33+
server.stderr.on("data", (data) => {
34+
stderr += data.toString();
35+
});
36+
37+
// Wait for the server to start
38+
timeout(2000)
39+
.then(() => {
40+
return Promise.all([
41+
fetch("http://127.0.0.1:4000/hello/hans", {
42+
signal: AbortSignal.timeout(5000),
43+
}),
44+
fetch(`http://127.0.0.1:4000/hello/${encodeURIComponent(`hans" //`)}`, {
45+
signal: AbortSignal.timeout(5000),
46+
}),
47+
]);
48+
})
49+
.then(([safeName, unsafeName]) => {
50+
t.equal(safeName.status, 200);
51+
t.equal(unsafeName.status, 500);
52+
t.match(stdout, /Starting agent/);
53+
t.match(stdout, /Zen has blocked a JavaScript injection/);
54+
})
55+
.catch((error) => {
56+
t.fail(error);
57+
})
58+
.finally(() => {
59+
server.kill();
60+
});
61+
});
62+
63+
t.test("it does not block in dry mode", (t) => {
64+
const server = spawn(`node`, [pathToApp, "4001"], {
65+
env: { ...process.env, AIKIDO_DEBUG: "true" },
66+
});
67+
68+
server.on("close", () => {
69+
t.end();
70+
});
71+
72+
let stdout = "";
73+
server.stdout.on("data", (data) => {
74+
stdout += data.toString();
75+
});
76+
77+
let stderr = "";
78+
server.stderr.on("data", (data) => {
79+
stderr += data.toString();
80+
});
81+
82+
// Wait for the server to start
83+
timeout(2000)
84+
.then(() =>
85+
Promise.all([
86+
fetch("http://127.0.0.1:4001/hello/hans", {
87+
signal: AbortSignal.timeout(5000),
88+
}),
89+
fetch(`http://127.0.0.1:4001/hello/${encodeURIComponent(`hans" //`)}`, {
90+
signal: AbortSignal.timeout(5000),
91+
}),
92+
])
93+
)
94+
.then(([safeName, unsafeName]) => {
95+
t.equal(safeName.status, 200);
96+
t.equal(unsafeName.status, 200);
97+
t.match(stdout, /Starting agent/);
98+
t.match(stdout, /Zen has detected a JavaScript injection/);
99+
})
100+
.catch((error) => {
101+
t.fail(error);
102+
})
103+
.finally(() => {
104+
server.kill();
105+
});
106+
});

end2end/tests/express-mongodb.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ t.test("it blocks in blocking mode", (t) => {
2121
});
2222

2323
server.on("error", (err) => {
24-
t.fail(err.message);
24+
t.fail(err);
2525
});
2626

2727
let stdout = "";
@@ -57,7 +57,7 @@ t.test("it blocks in blocking mode", (t) => {
5757
t.match(stderr, /Zen has blocked a NoSQL injection/);
5858
})
5959
.catch((error) => {
60-
t.fail(error.message);
60+
t.fail(error);
6161
})
6262
.finally(() => {
6363
server.kill();
@@ -106,7 +106,7 @@ t.test("it does not block in dry mode", (t) => {
106106
t.notMatch(stderr, /Zen has blocked a NoSQL injection/);
107107
})
108108
.catch((error) => {
109-
t.fail(error.message);
109+
t.fail(error);
110110
})
111111
.finally(() => {
112112
server.kill();
@@ -141,7 +141,7 @@ t.test("it blocks in blocking mode (with open telemetry enabled)", (t) => {
141141
});
142142

143143
server.on("error", (err) => {
144-
t.fail(err.message);
144+
t.fail(err);
145145
});
146146

147147
let stdout = "";
@@ -174,7 +174,7 @@ t.test("it blocks in blocking mode (with open telemetry enabled)", (t) => {
174174
t.match(stderr, /Zen has blocked a NoSQL injection/);
175175
})
176176
.catch((error) => {
177-
t.fail(error.message);
177+
t.fail(error);
178178
})
179179
.finally(() => {
180180
server.kill("SIGINT");
@@ -237,7 +237,7 @@ t.test("it does not block in dry mode (with open telemetry enabled)", (t) => {
237237
t.notMatch(stderr, /Zen has blocked a NoSQL injection/);
238238
})
239239
.catch((error) => {
240-
t.fail(error.message);
240+
t.fail(error);
241241
})
242242
.finally(() => {
243243
server.kill("SIGINT");

sample-apps/express-mongodb/app.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,24 @@ async function main(port) {
186186
})
187187
);
188188

189+
app.get(
190+
"/hello/:name",
191+
asyncHandler(async (req, res) => {
192+
const { name } = req.params;
193+
194+
if (!name) {
195+
return res.status(400).end();
196+
}
197+
198+
// This code is vulnerable to code injection
199+
// This is just a sample app to demonstrate the vulnerability
200+
// Do not use this code in production
201+
const welcome = new Function(`return "Hello, your name is ${name}!"`);
202+
203+
res.send(welcome());
204+
})
205+
);
206+
189207
return new Promise((resolve, reject) => {
190208
try {
191209
app.listen(port, () => {

0 commit comments

Comments
 (0)