Skip to content

Commit

Permalink
Add xml2js end2end tests
Browse files Browse the repository at this point in the history
  • Loading branch information
timokoessler committed Jun 20, 2024
1 parent 6b81acd commit 3d0108a
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 11 deletions.
120 changes: 120 additions & 0 deletions end2end/tests/hono-xml.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
const t = require("tap");
const { spawn } = require("child_process");
const { resolve } = require("path");
const timeout = require("../timeout");

const pathToApp = resolve(__dirname, "../../sample-apps/hono-xml", "app.js");

t.test("it blocks in blocking mode", (t) => {
const server = spawn(`node`, [pathToApp, "4000"], {
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCKING: "true" },
});

server.on("close", () => {
t.end();
});

server.on("error", (err) => {
t.fail(err.message);
});

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://localhost:4000/add", {
method: "POST",
body: "<cat><name>Njuska'); DELETE FROM cats;-- H</name></cat>",
headers: {
"Content-Type": "application/xml",
},
signal: AbortSignal.timeout(5000),
}),
fetch("http://localhost:4000/add", {
method: "POST",
body: "<cat><name>Miau</name></cat>",
headers: {
"Content-Type": "application/xml",
},
signal: AbortSignal.timeout(5000),
}),
]);
})
.then(([sqlInjection, normalAdd]) => {
t.equal(sqlInjection.status, 500);
t.equal(normalAdd.status, 200);
t.match(stdout, /Starting agent/);
t.match(stderr, /Aikido firewall has blocked an SQL injection/);
})
.catch((error) => {
t.fail(error.message);
})
.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://localhost:4001/add", {
method: "POST",
body: "<cat><name>Njuska'); DELETE FROM cats;-- H</name></cat>",
headers: {
"Content-Type": "application/xml",
},
signal: AbortSignal.timeout(5000),
}),
fetch("http://localhost:4001/add", {
method: "POST",
body: "<cat><name>Miau</name></cat>",
headers: {
"Content-Type": "application/xml",
},
signal: AbortSignal.timeout(5000),
}),
])
)
.then(([sqlInjection, normalAdd]) => {
t.equal(sqlInjection.status, 200);
t.equal(normalAdd.status, 200);
t.match(stdout, /Starting agent/);
t.notMatch(stderr, /Aikido firewall has blocked an SQL injection/);
})
.catch((error) => {
t.fail(error.message);
})
.finally(() => {
server.kill();
});
});
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions sample-apps/hono-xml/Cats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Cats {
constructor(db) {
this.db = db;
}

async add(name) {
// This is unsafe! This is for demo purposes only, you should use parameterized queries.
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`;");

return cats.map((row) => row.petname);
}
}

module.exports = Cats;
45 changes: 37 additions & 8 deletions sample-apps/hono-xml/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ require("@aikidosec/firewall");
const xml2js = require("xml2js");
const { serve } = require("@hono/node-server");
const { Hono } = require("hono");
const { createConnection } = require("./db");
const Aikido = require("@aikidosec/firewall/context");
const Cats = require("./Cats");

async function main() {
const app = new Hono();
const db = await createConnection();
const cats = new Cats(db);

app.use(async (c, next) => {
Aikido.setUser({
Expand All @@ -18,19 +22,37 @@ async function main() {
});

app.get("/", async (c) => {
const catNames = await cats.getAll();
return c.html(
`
<html lang="en">
<body>
<h1>Vulnerable app using XML</h1>
<ul id="list">
${catNames.map((name) => `<li>${name}</li>`).join("")}
</ul>
<form id="add-cat">
<label for="search">Add a new cat</label>
<input type="text" name="petname">
<input type="submit" value="Add" />
</form>
<p>SQL Injection: '); DELETE FROM cats;-- H</p>
<a href="/clear">Clear all cats</a>
<script>
document.addEventListener("DOMContentLoaded", () => {
fetch("/search", {
method: "POST",
body: "<search><cat><name>Test</name></cat></search>"
}).then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
const form = document.getElementById("add-cat");
form.addEventListener("submit", async (event) => {
event.preventDefault();
fetch("/add", {
method: "POST",
body: "<cat><name>" + form.petname.value + "</name></cat>",
}).then(response => response.json())
.then(data => {
window.location.reload();
})
.catch(error => document.getElementById("list").innerHTML = "<li>Error</li>");
window.location.reload();
});
});
</script>
</body>
Expand All @@ -39,7 +61,7 @@ async function main() {
);
});

app.post("/search", async (c) => {
app.post("/add", async (c) => {
const body = await c.req.text();

let result;
Expand All @@ -50,7 +72,14 @@ async function main() {
return c.json({ error: "Invalid XML" }, 400);
}

return c.json(result);
await cats.add(result.cat.name[0]);

return c.json({ success: true });
});

app.get("/clear", async (c) => {
await db.execute("DELETE FROM cats;");
return c.redirect("/");
});

return app;
Expand Down
25 changes: 25 additions & 0 deletions sample-apps/hono-xml/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const mysql = require("mysql2/promise");

async function createConnection() {
// Normally you'd use environment variables for this
const connection = await mysql.createConnection({
host: "localhost",
user: "root",

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "root" is used as
user name
.
password: "mypassword",
database: "catsdb",
port: 27015,
multipleStatements: true,
});

await connection.execute(`
CREATE TABLE IF NOT EXISTS cats (
petname varchar(255)
);
`);

return connection;
}

module.exports = {
createConnection,
};
1 change: 1 addition & 0 deletions sample-apps/hono-xml/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@aikidosec/firewall": "file:../../build",
"@hono/node-server": "^1.11.2",
"hono": "^4.4.2",
"mysql2": "^3.10.1",
"xml2js": "^0.6.2"
}
}

0 comments on commit 3d0108a

Please sign in to comment.