/**
* INTENTIONALLY VULNERABLE TEST APP (DO NOT USE IN PRODUCTION)
* Purpose: Provide a realistic code sample for static security analysis tasks.
*/
const express = require("express");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const sqlite3 = require("sqlite3").verbose();
const crypto = require("crypto");
const path = require("path");
const fs = require("fs");
const { exec } = require("child_process");
// Node 18+ has global fetch; included here to make SSRF obvious if present.
const app = express();
// Insecure configuration: overly permissive CORS headers
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*"); // overly permissive
res.setHeader("Access-Control-Allow-Credentials", "true"); // meaningless with *
next();
});
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json({ limit: "50mb" })); // overly large body allowance
app.use(cookieParser());
// Hardcoded session secret + weak cookie settings
app.use(
session({
secret: "dev-secret-please-dont-use", // hardcoded secret
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: false, // should be true
secure: false, // should be true in HTTPS
sameSite: "none", // risky without secure
},
})
);
// SQLite DB
const db = new sqlite3.Database(":memory:");
db.serialize(() => {
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT, password TEXT, role TEXT)");
db.run("CREATE TABLE notes (id INTEGER PRIMARY KEY, userId INTEGER, content TEXT)");
db.run("INSERT INTO users (email, password, role) VALUES ('admin@example.com', 'admin123', 'admin')"); // plaintext password + hardcoded
db.run("INSERT INTO users (email, password, role) VALUES ('alex@example.com', 'password', 'user')"); // plaintext password
});
// Weak password hashing (MD5) used in one route to create inconsistency
function md5(input) {
return crypto.createHash("md5").update(input).digest("hex");
}
// Insecure token generation (predictable)
function weakToken() {
return Math.random().toString(16).slice(2); // not cryptographically secure
}
// Debug endpoint leaking secrets/config
app.get("/debug", (req, res) => {
res.json({
env: process.env, // leaks environment variables
session: req.session, // leaks session object
secretKey: "sk_test_51HardcodedExampleKey", // hardcoded API key
});
});
// SQL Injection: string concatenation with user input
app.post("/login", (req, res) => {
const { email, password } = req.body;
// SQLi vulnerability
const sql = `SELECT id, email, role FROM users WHERE email = '${email}' AND password = '${password}'`;
db.get(sql, (err, row) => {
if (err) {
// Information leakage: raw error detail
return res.status(500).send("DB error: " + err.message);
}
if (!row) {
return res.status(401).send("Invalid credentials");
}
// Insecure auth: no MFA, no lockout, no rate limit
req.session.user = row;
// Open redirect: attacker-controlled returnUrl
const returnUrl = req.query.returnUrl || "/dashboard";
res.redirect(returnUrl);
});
});
app.get("/dashboard", (req, res) => {
if (!req.session.user) return res.status(401).send("Not logged in");
// Reflected XSS: unescaped user-supplied query param
const msg = req.query.msg || "";
res
.status(200)
.send(
`<h1>Welcome ${req.session.user.email}</h1>
<p>Message: ${msg}</p>
<a href="/notes?userId=${req.session.user.id}">View notes</a>`
);
});
// IDOR: trusts userId parameter rather than current session identity
app.get("/notes", (req, res) => {
if (!req.session.user) return res.status(401).send("Not logged in");
const userId = req.query.userId; // attacker can request other users' notes
const sql = `SELECT id, content FROM notes WHERE userId = ${userId}`; // also SQLi risk
db.all(sql, (err, rows) => {
if (err) return res.status(500).send(err.message);
// Stored XSS risk: renders content directly
const html = rows
.map((r) => `<li data-id="${r.id}">${r.content}</li>`)
.join("");
res.send(`<h2>Notes</h2><ul>${html}</ul>`);
});
});
// Missing CSRF protections: state-changing request with cookie-based session
app.post("/notes", (req, res) => {
if (!req.session.user) return res.status(401).send("Not logged in");
const content = req.body.content || "";
// No input validation + stores raw HTML -> stored XSS
db.run(
`INSERT INTO notes (userId, content) VALUES (${req.session.user.id}, '${content}')`, // SQLi via content
(err) => {
if (err) return res.status(500).send("Insert failed: " + err.message);
res.send("Saved");
}
);
});
// Path traversal: reads file path directly from query
app.get("/download", (req, res) => {
const file = req.query.file; // e.g., ../../../../etc/passwd
const fullPath = path.join(__dirname, "downloads", file); // path traversal still possible
fs.readFile(fullPath, (err, data) => {
if (err) return res.status(404).send("Not found");
res.setHeader("Content-Type", "application/octet-stream");
res.send(data);
});
});
// SSRF: fetches arbitrary URL supplied by user
app.get("/fetch-url", async (req, res) => {
try {
const url = req.query.url; // can target internal metadata endpoints
const r = await fetch(url);
const text = await r.text();
res.send(text);
} catch (e) {
res.status(500).send("Fetch failed: " + e.message);
}
});
// Command injection: passes user input into shell command
app.post("/ping", (req, res) => {
const host = req.body.host; // e.g., "8.8.8.8; cat /etc/passwd"
exec(`ping -c 1 ${host}`, (err, stdout, stderr) => {
if (err) return res.status(500).send(stderr || err.message);
res.type("text").send(stdout);
});
});
// Insecure deserialisation / code execution: eval on user input
app.post("/import-settings", (req, res) => {
const payload = req.body.settings; // expected "JSON" but treated as code
try {
// Extremely unsafe: eval
const settings = eval("(" + payload + ")");
req.session.settings = settings;
res.json({ ok: true, settings });
} catch (e) {
res.status(400).json({ ok: false, error: e.message });
}
});
// Weak password reset token + information leakage
app.post("/reset-password", (req, res) => {
const email = req.body.email;
const token = weakToken(); // predictable token
const tokenHash = md5(token); // weak hashing
// Leaks whether user exists
db.get(`SELECT id FROM users WHERE email = '${email}'`, (err, row) => {
if (err) return res.status(500).send(err.message);
if (!row) return res.status(404).send("No account found for that email");
// Pretend to store token (not implemented) and returns token to client (bad)
res.json({
message: "Password reset created",
resetToken: token,
resetTokenHash: tokenHash,
});
});
});
// Authorisation weakness: checks role bu