const express = require('express');
const bodyParser = require('body-parser');
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3000;
const DB_FILE = path.join(__dirname, 'lab.db');
const DUMMY_SQL_FILE = path.join(__dirname, 'dummy_data.sql');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Very simple HTML templates (no view engine)
function layout(title, body) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
${body}
</body>
</html>`;
}
// Initialize database if not present
function initDatabase() {
const exists = fs.existsSync(DB_FILE);
const db = new sqlite3.Database(DB_FILE);
if (!exists) {
console.log('Database not found, initializing from dummy_data.sql...');
const sql = fs.readFileSync(DUMMY_SQL_FILE, 'utf8');
db.exec(sql, (err) => {
if (err) {
console.error('Error initializing database:', err);
} else {
console.log('Database initialized with dummy data.');
}
});
}
return db;
}
const db = initDatabase();
// Home page with links to features
app.get('/', (req, res) => {
const body = `
<ul>
<li><a href="/search?q=test">Search (Reflected XSS target)</a></li>
<li><a href="/feedback">Feedback (Stored XSS target)</a></li>
<li><a href="/read-file?file=server.js">Read File (Path Traversal / LFI target)</a></li>
<li><a href="/debug-users">Debug Users (view users table)</a></li>
</ul>
<p>API endpoints:</p>
<ul>
<li><code>GET /api/products?id=1</code> (SQL Injection target)</li>
<li><code>POST /api/login</code> (SQL Injection target)</li>
</ul>
`;
res.send(layout('lab.dctm.ir Vulnerable App', body));
});
// Vulnerable SQLi endpoint for sqlmap: GET /api/products?id=1
app.get('/api/products', (req, res) => {
const id = req.query.id || '1';
// INTENTIONALLY VULNERABLE: raw string concatenation
const query = "SELECT id, name, description, price, secret_note FROM products WHERE id = " + id + ";";
console.log('Executing vulnerable query:', query);
db.all(query, (err, rows) => {
if (err) {
return res.status(500).json({ error: err.toString(), query });
}
res.json({ query, rows });
});
});
// Vulnerable SQLi login: POST /api/login
app.post('/api/login', (req, res) => {
const username = req.body.username || '';
const password = req.body.password || '';
// INTENTIONALLY VULNERABLE: raw string concatenation
const query =
"SELECT id, username, is_admin, secret_token FROM users WHERE username = '" +
username +
"' AND password = '" +
password +
"';";
console.log('Executing vulnerable login query:', query);
db.get(query, (err, row) => {
if (err) {
return res.status(500).send(layout('Login Error', `<p>${err.toString()}</p><pre>${query}</pre>`));
}
if (!row) {
return res.send(
layout(
'Login Failed',
`<p>Invalid credentials for user: <b>${username}</b></p><pre>${query}</pre>`
)
);
}
res.send(
layout(
'Login Successful',
`<p>Welcome, <b>${row.username}</b>!</p>
<p>is_admin: ${row.is_admin}</p>
<p>secret_token: ${row.secret_token}</p>
<pre>${query}</pre>`
)
);
});
});
// Reflected XSS target: /search?q=test
app.get('/search', (req, res) => {
const q = req.query.q || '';
// INTENTIONALLY VULNERABLE: reflected straight into HTML
const body = `
<form method="GET" action="/search">
<label>Search query: <input type="text" name="q" value="${q}"></label>
<button type="submit">Search</button>
</form>
<p>Results for: <b>${q}</b></p>
<p>(No actual search is performed. This is a reflected XSS target for tools like Dalfox and XSStrike.)</p>
`;
res.send(layout('Search', body));
});
// Stored XSS target: Feedback form
app.get('/feedback', (req, res) => {
const body = `
<form method="POST" action="/feedback">
<p><label>Name: <input type="text" name="name"></label></p>
<p><label>Message: <textarea name="message" rows="4" cols="40"></textarea></label></p>
<button type="submit">Submit</button>
</form>
<h2>Previous Feedback</h2>
`;
// INTENTIONALLY VULNERABLE: feedback messages are rendered without encoding
db.all('SELECT name, message, created_at FROM feedback ORDER BY id DESC LIMIT 20;', (err, rows) => {
if (err) {
return res.send(layout('Feedback Error', `<p>${err.toString()}</p>`));
}
let list = '<ul>';
rows.forEach((row) => {
list += `<li><b>${row.name}</b> (${row.created_at}): ${row.message}</li>`;
});
list += '</ul>';
res.send(layout('Feedback', body + list));
});
});
app.post('/feedback', (req, res) => {
const name = req.body.name || 'Anonymous';
const message = req.body.message || '';
// Simple insert (no sanitization of content; intentional for stored XSS)
const stmt = db.prepare('INSERT INTO feedback (name, message, created_at) VALUES (?, ?, datetime("now"))');
stmt.run(name, message, (err) => {
if (err) {
return res.send(layout('Feedback Error', `<p>${err.toString()}</p>`));
}
res.redirect('/feedback');
});
stmt.finalize();
});
// File inclusion / path traversal target: /read-file?file=server.js
app.get('/read-file', (req, res) => {
const file = req.query.file || 'server.js';
// INTENTIONALLY VULNERABLE: no sanitization, resolves relative to project root
const targetPath = path.join(__dirname, file);
fs.readFile(targetPath, 'utf8', (err, data) => {
if (err) {
return res
.status(404)
.send(layout('Read File Error', `<p>Could not read file: ${file}</p><p>${err.toString()}</p>`));
}
res.send(
layout(
`Reading file: ${file}`,
`<pre>${data.replace(/</g, '<').replace(/>/g, '>')}</pre>
<p>Try path traversal like <code>?file=../server.js</code> or other files on disk.</p>`
)
);
});
});
// Simple debug endpoint to see users table (not protected)
app.get('/debug-users', (req, res) => {
db.all('SELECT id, username, password, is_admin, secret_token FROM users;', (err, rows) => {
if (err) {
return res.send(layout('Debug Users Error', `<p>${err.toString()}</p>`));
}
let table = '<table border="1"><tr><th>id</th><th>username</th><th>password</th><th>is_admin</th><th>secret_token</th></tr>';
rows.forEach((row) => {
table += `<tr><td>${row.id}</td><td>${row.username}</td><td>${row.password}</td><td>${row.is_admin}</td><td>${row.secret_token}</td></tr>`;
});
table += '</table>';
res.send(layout('Debug Users', table));
});
});
app.listen(PORT, () => {
console.log(`Vulnerable lab app listening on http://127.0.0.1:${PORT}`);
});
Try path traversal like ?file=../server.js or other files on disk.