diff --git a/end2end/tests/express-mongodb.code-injection.test.js b/end2end/tests/express-mongodb.code-injection.test.js new file mode 100644 index 000000000..0a41c0df5 --- /dev/null +++ b/end2end/tests/express-mongodb.code-injection.test.js @@ -0,0 +1,106 @@ +const t = require("tap"); +const { spawn } = require("child_process"); +const { resolve } = require("path"); +const timeout = require("../timeout"); + +const pathToApp = resolve( + __dirname, + "../../sample-apps/express-mongodb", + "app.js" +); + +t.setTimeout(60000); + +t.test("it blocks in blocking mode", (t) => { + const server = spawn(`node`, [pathToApp, "4000"], { + env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCK: "true" }, + }); + + server.on("close", () => { + t.end(); + }); + + server.on("error", (err) => { + t.fail(err); + }); + + let stdout = ""; + server.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + let stderr = ""; + server.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + // Wait for the server to start + timeout(2000) + .then(() => { + return Promise.all([ + fetch("http://127.0.0.1:4000/hello/hans", { + signal: AbortSignal.timeout(5000), + }), + fetch(`http://127.0.0.1:4000/hello/${encodeURIComponent(`hans" //`)}`, { + signal: AbortSignal.timeout(5000), + }), + ]); + }) + .then(([safeName, unsafeName]) => { + t.equal(safeName.status, 200); + t.equal(unsafeName.status, 500); + t.match(stdout, /Starting agent/); + t.match(stdout, /Zen has blocked a JavaScript injection/); + }) + .catch((error) => { + t.fail(error); + }) + .finally(() => { + server.kill(); + }); +}); + +t.test("it does not block in dry mode", (t) => { + const server = spawn(`node`, [pathToApp, "4001"], { + env: { ...process.env, AIKIDO_DEBUG: "true" }, + }); + + server.on("close", () => { + t.end(); + }); + + let stdout = ""; + server.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + let stderr = ""; + server.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + // Wait for the server to start + timeout(2000) + .then(() => + Promise.all([ + fetch("http://127.0.0.1:4001/hello/hans", { + signal: AbortSignal.timeout(5000), + }), + fetch(`http://127.0.0.1:4001/hello/${encodeURIComponent(`hans" //`)}`, { + signal: AbortSignal.timeout(5000), + }), + ]) + ) + .then(([safeName, unsafeName]) => { + t.equal(safeName.status, 200); + t.equal(unsafeName.status, 200); + t.match(stdout, /Starting agent/); + t.match(stdout, /Zen has detected a JavaScript injection/); + }) + .catch((error) => { + t.fail(error); + }) + .finally(() => { + server.kill(); + }); +}); diff --git a/end2end/tests/express-mongodb.test.js b/end2end/tests/express-mongodb.test.js index 70c309fda..fed139393 100644 --- a/end2end/tests/express-mongodb.test.js +++ b/end2end/tests/express-mongodb.test.js @@ -21,7 +21,7 @@ t.test("it blocks in blocking mode", (t) => { }); server.on("error", (err) => { - t.fail(err.message); + t.fail(err); }); let stdout = ""; @@ -57,7 +57,7 @@ t.test("it blocks in blocking mode", (t) => { t.match(stderr, /Zen has blocked a NoSQL injection/); }) .catch((error) => { - t.fail(error.message); + t.fail(error); }) .finally(() => { server.kill(); @@ -106,7 +106,7 @@ t.test("it does not block in dry mode", (t) => { t.notMatch(stderr, /Zen has blocked a NoSQL injection/); }) .catch((error) => { - t.fail(error.message); + t.fail(error); }) .finally(() => { server.kill(); @@ -141,7 +141,7 @@ t.test("it blocks in blocking mode (with open telemetry enabled)", (t) => { }); server.on("error", (err) => { - t.fail(err.message); + t.fail(err); }); let stdout = ""; @@ -174,7 +174,7 @@ t.test("it blocks in blocking mode (with open telemetry enabled)", (t) => { t.match(stderr, /Zen has blocked a NoSQL injection/); }) .catch((error) => { - t.fail(error.message); + t.fail(error); }) .finally(() => { server.kill("SIGINT"); @@ -237,7 +237,7 @@ t.test("it does not block in dry mode (with open telemetry enabled)", (t) => { t.notMatch(stderr, /Zen has blocked a NoSQL injection/); }) .catch((error) => { - t.fail(error.message); + t.fail(error); }) .finally(() => { server.kill("SIGINT"); diff --git a/sample-apps/express-mongodb/app.js b/sample-apps/express-mongodb/app.js index 0c318f0e4..b8234bc32 100644 --- a/sample-apps/express-mongodb/app.js +++ b/sample-apps/express-mongodb/app.js @@ -186,6 +186,24 @@ async function main(port) { }) ); + app.get( + "/hello/:name", + asyncHandler(async (req, res) => { + const { name } = req.params; + + if (!name) { + return res.status(400).end(); + } + + // This code is vulnerable to code injection + // This is just a sample app to demonstrate the vulnerability + // Do not use this code in production + const welcome = new Function(`return "Hello, your name is ${name}!"`); + + res.send(welcome()); + }) + ); + return new Promise((resolve, reject) => { try { app.listen(port, () => {