From 14f0a5d7f326a7cba14259dec42929eafca1536c Mon Sep 17 00:00:00 2001 From: Hans Ott Date: Mon, 22 Apr 2024 18:45:03 +0200 Subject: [PATCH] Add failing test for SQL injection via route parameter --- end2end/tests/express-mysql2.test.js | 22 ++++++++++++++++++---- sample-apps/express-mysql2/Cats.js | 9 +++++++++ sample-apps/express-mysql2/app.js | 21 +++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/end2end/tests/express-mysql2.test.js b/end2end/tests/express-mysql2.test.js index 649762ba1..8cc4611b3 100644 --- a/end2end/tests/express-mysql2.test.js +++ b/end2end/tests/express-mysql2.test.js @@ -42,13 +42,20 @@ t.test("it blocks in blocking mode", (t) => { signal: AbortSignal.timeout(5000), } ), + fetch( + `http://localhost:4000/cats/${encodeURIComponent("Njuska'; DELETE FROM cats;-- H")}`, + { + signal: AbortSignal.timeout(5000), + } + ), fetch("http://localhost:4000/?petname=Njuska", { signal: AbortSignal.timeout(5000), }), ]); }) - .then(([noSQLInjection, normalSearch]) => { - t.equal(noSQLInjection.status, 500); + .then(([sqlInjection, sqlInjection2, normalSearch]) => { + t.equal(sqlInjection.status, 500); + t.equal(sqlInjection2.status, 500); t.equal(normalSearch.status, 200); t.match(stdout, /Starting agent/); t.match(stderr, /Aikido runtime has blocked a SQL injection/); @@ -90,13 +97,20 @@ t.test("it does not block in dry mode", (t) => { signal: AbortSignal.timeout(5000), } ), + fetch( + `http://localhost:4001/cats/${encodeURIComponent("Njuska'; DELETE FROM cats;-- H")}`, + { + signal: AbortSignal.timeout(5000), + } + ), fetch("http://localhost:4001/?petname=Njuska", { signal: AbortSignal.timeout(5000), }), ]) ) - .then(([noSQLInjection, normalSearch]) => { - t.equal(noSQLInjection.status, 200); + .then(([sqlInjection, sqlInjection2, normalSearch]) => { + t.equal(sqlInjection.status, 200); + t.equal(sqlInjection2.status, 200); t.equal(normalSearch.status, 200); t.match(stdout, /Starting agent/); t.notMatch(stderr, /Aikido runtime has blocked a SQL injection/); diff --git a/sample-apps/express-mysql2/Cats.js b/sample-apps/express-mysql2/Cats.js index f992a553d..084b48548 100644 --- a/sample-apps/express-mysql2/Cats.js +++ b/sample-apps/express-mysql2/Cats.js @@ -8,6 +8,15 @@ class Cats { await this.db.query(`INSERT INTO cats(petname) VALUES ('${name}');`); } + async byName(name) { + // This is unsafe! This is for demo purposes only, you should use parameterized queries. + const [cats] = await this.db.query( + `SELECT petname FROM cats WHERE petname = '${name}'` + ); + + return cats.map((row) => row.petname); + } + async getAll() { const [cats] = await this.db.execute("SELECT petname FROM `cats`;"); diff --git a/sample-apps/express-mysql2/app.js b/sample-apps/express-mysql2/app.js index e1a2feeb5..18c83c40d 100644 --- a/sample-apps/express-mysql2/app.js +++ b/sample-apps/express-mysql2/app.js @@ -71,6 +71,27 @@ async function main(port) { }) ); + app.get( + "/cats/:name", + asyncHandler(async (req, res) => { + const found = await cats.byName(req.params.name); + + if (found.length === 0) { + return res.status(404).send("Cat not found"); + } + + const cat = found[0]; + + res.send(` + + +

${escape(cat)}

+ + + `); + }) + ); + return new Promise((resolve, reject) => { try { app.listen(port, () => {