Skip to content

Instantly share code, notes, and snippets.

@akoskm
Last active October 25, 2024 16:44
Show Gist options
  • Save akoskm/2563b75f6e677b7f25cfa2b4b27677ef to your computer and use it in GitHub Desktop.
Save akoskm/2563b75f6e677b7f25cfa2b4b27677ef to your computer and use it in GitHub Desktop.
Path Traversal Attack Demonstration

Install with npm i and run the server with npm start.

Depending on where you put your project, you might have to alter the path in the query params, but eventually you'll get the /etc/passwd file downloaded using the unsafe access:

image

const express = require("express");
const path = require("path");
const fs = require("fs").promises;
const app = express();
const PORT = 3000;
// Create some dummy files for demonstration
async function setupDummyFiles() {
// Create user_files directory if it doesn't exist
await fs.mkdir("user_files", { recursive: true });
// Create a dummy file in user_files
await fs.writeFile(
"user_files/safe_file.txt",
"This is a safe file you should be able to access",
);
// Create a dummy .env file for demonstration
await fs.writeFile(
".env",
"SECRET_KEY=this_should_not_be_accessible\nDB_PASSWORD=super_secret",
);
console.log("✅ Demo files created successfully");
}
// ⚠️ Vulnerable endpoint
app.get("/download/unsafe", (req, res) => {
const userFile = req.query.filename;
const filePath = path.join("user_files", userFile);
res.sendFile(path.join(process.cwd(), filePath), (err) => {
if (err) {
res.status(404).send("File not found or not accessible");
}
});
});
// ✅ Protected endpoint
app.get("/download/safe", (req, res) => {
const userFile = req.query.filename;
// Sanitize the filename
const sanitized = path
.basename(userFile)
.replace(/[^a-zA-Z0-9.-_]/g, "_")
.substring(0, 255);
// Whitelist approach - only allow access to specific directory
const safePath = path.join(process.cwd(), "user_files", sanitized);
if (!safePath.startsWith(path.join(process.cwd(), "user_files"))) {
return res.status(403).send("Access denied");
}
res.sendFile(safePath, (err) => {
if (err) {
res.status(404).send("File not found or not accessible");
}
});
});
// Add a root route to show instructions
app.get("/", (req, res) => {
res.send(`
<h1>Path Traversal Demo</h1>
<h2>Try these attacks:</h2>
<ul>
<li><a href="/download/unsafe?filename=safe_file.txt">Safe access</a></li>
<li><a href="/download/unsafe?filename=../.env">Access .env file (attack)</a></li>
<li><a href="/download/unsafe?filename=../../etc/passwd">Access passwd file (attack)</a></li>
</ul>
<h2>Try the protected endpoint:</h2>
<ul>
<li><a href="/download/safe?filename=safe_file.txt">Safe access</a></li>
<li><a href="/download/safe?filename=../.env">Try to access .env (should fail)</a></li>
</ul>
`);
});
// Start the server
async function startServer() {
try {
await setupDummyFiles();
app.listen(PORT, () => {
console.log(`
🚀 Server is running!
Try these curl commands:
curl "http://localhost:${PORT}/download/unsafe?filename=../.env"
curl "http://localhost:${PORT}/download/unsafe?filename=../../etc/passwd"
Or open in browser:
http://localhost:${PORT}
Press Ctrl+C to stop the server
`);
});
} catch (err) {
console.error("Failed to start server:", err);
}
}
startServer();
{
"name": "attack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.21.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment