Diff

Created Diff never expires
5 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
381 lines
24 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
399 lines
// A simple Express.js application to demonstrate user registration, login,
// A simple Express.js application to demonstrate user registration, login,
// session handling, and safe rendering of user-controlled data.
// session handling, and safe rendering of user-controlled data.
//
//
// === WARNING: FOR CTF (CAPTURE THE FLAG) USE ONLY ===
// === WARNING: FOR CTF (CAPTURE THE FLAG) USE ONLY ===
// This code stores passwords in plaintext. This is a severe security vulnerability
// This code stores passwords in plaintext. This is a severe security vulnerability
// and should NEVER be used in a production or real-world application.
// and should NEVER be used in a production or real-world application.
//
//
// This is done to fulfill the user's request for a CTF scenario.
// This is done to fulfill the user's request for a CTF scenario.


// 1. Import necessary libraries
// 1. Import necessary libraries
// Note: You will need to run `npm install express express-session` to use this code.
// Note: You will need to run `npm install express express-session` to use this code.
// bcrypt has been removed as per the CTF request.
// bcrypt has been removed as per the CTF request.
const express = require('express');
const express = require('express');
const session = require('express-session');
const session = require('express-session');
const crypto = require('crypto');

const app = express();
const app = express();
const PORT = 3000;
const PORT = 3000;
const FLAG = process.env.FLAG || "justCTF{example_flag}"
const SECRET = crypto.randomBytes(24).toString('hex');


// 2. Middleware to parse request bodies (for form data)
// 2. Middleware to parse request bodies (for form data)
app.use(express.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }));


// 3. Configure session middleware
// 3. Configure session middleware
app.use(session({
app.use(session({
secret: 'a_very_secure_secret_key_for_session', // In a real app, this should be an environment variable
secret: SECRET,
resave: false,
resave: false,
saveUninitialized: true,
saveUninitialized: true,
cookie: { secure: false } // Set to true if using HTTPS
cookie: { secure: false } // Set to true if using HTTPS
}));
}));


// 4. In-memory user data store (simulating a database)
// 4. In-memory user data store (simulating a database)
// In a real application, you would use a database like MongoDB or PostgreSQL.
// In a real application, you would use a database like MongoDB or PostgreSQL.
// For this CTF scenario, passwords are stored in plaintext.
// For this CTF scenario, passwords are stored in plaintext.
const users = {}; // Stores { username: { password, userThemeConfig } }
const users = {}; // Stores { username: { password, userThemeConfig, isAdmin } }


// 5. A simple function to safely escape HTML to prevent XSS attacks.
// 5. A simple function to safely escape HTML to prevent XSS attacks.
const escapeHtml = (unsafe) => {
const escapeHtml = (unsafe) => {
if (typeof unsafe !== 'string') return unsafe;
if (typeof unsafe !== 'string') return unsafe;
return unsafe
return unsafe
.replace(/&/g, "&")
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
.replace(/'/g, "&#039;");
};
};


// 6. A function to recursively merge objects
// 6. A function to recursively merge objects
const deepMerge = (target, source) => {
const deepMerge = (target, source) => {
for (const key in source) {
for (const key in source) {
if (source[key] instanceof Object && key in target) {
if (source[key] instanceof Object && key in target) {
Object.assign(source[key], deepMerge(target[key], source[key]));
Object.assign(source[key], deepMerge(target[key], source[key]));
}
}
}
}
Object.assign(target || {}, source);
Object.assign(target || {}, source);
return target;
return target;
};
};


// 7. A function to parse a query string with dot-notation keys.
// 7. A function to parse a query string with dot-notation keys.
const parseQueryParams = (queryString) => {
const parseQueryParams = (queryString) => {
if (typeof queryString !== 'string') {
if (typeof queryString !== 'string') {
return {};
return {};
}
}
const cleanString = queryString.startsWith('?') ? queryString.substring(1) : queryString;
const cleanString = queryString.startsWith('?') ? queryString.substring(1) : queryString;
const params = new URLSearchParams(cleanString);
const params = new URLSearchParams(cleanString);
const result = {};
const result = {};
for (const [key, value] of params.entries()) {
for (const [key, value] of params.entries()) {
const path = key.split('.');
const path = key.split('.');
let current = result;
let current = result;
for (let i = 0; i < path.length; i++) {
for (let i = 0; i < path.length; i++) {
const part = path[i];
let part = path[i];
// Protect against Prototype Pollution vulnerability
if(['__proto__', 'prototype', 'constructor'].includes(part)){
part = '__unsafe$' + part;
}
if (i === path.length - 1) {
if (i === path.length - 1) {
current[part] = value;
current[part] = value;
} else {
} else {
if (!current[part] || typeof current[part] !== 'object') {
if (!current[part] || typeof current[part] !== 'object') {
current[part] = {};
current[part] = {};
}
}
current = current[part];
current = current[part];
}
}
}
}
}
}
return result;
return result;
};
};




// 8. Authentication Middleware
// 8. Authentication Middleware
// This function checks if a user is logged in before allowing access to a route.
// This function checks if a user is logged in before allowing access to a route.
const isAuthenticated = (req, res, next) => {
const isAuthenticated = (req, res, next) => {
if (req.session.userId) {
if (req.session.userId) {
next(); // User is authenticated, proceed to the next middleware/route handler
next(); // User is authenticated, proceed to the next middleware/route handler
} else {
} else {
res.redirect('/login'); // User is not authenticated, redirect to login page
res.redirect('/login'); // User is not authenticated, redirect to login page
}
}
};
};


// 9. Default Theme Configuration
// 9. Default Theme Configuration
const defaultThemeConfig = {
const defaultThemeConfig = {
theme: {
theme: {
primaryColor: '#8E24AA', // A nice shade of purple
primaryColor: '#8E24AA', // A nice shade of purple
secondaryColor: '#FFC107', // An amber yellow
secondaryColor: '#FFC107', // An amber yellow
fontSize: '18px',
fontSize: '18px',
fontFamily: 'Arial, sans-serif'
fontFamily: 'Arial, sans-serif'
}
}
};
};


// 10. Helper function to generate a styled HTML page
// 10. Helper function to generate a styled HTML page
const generateThemedPage = (pageBody, themeConfig, title = 'Theme Configuration App') => {
const generateThemedPage = (pageBody, themeConfig, title = 'Theme Configuration App') => {
return `
return `
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<html lang="en">
<head>
<head>
<meta charset="UTF-8">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${escapeHtml(title)}</title>
<title>${escapeHtml(title)}</title>
<style>
<style>
body {
body {
font-family: ${escapeHtml(themeConfig.theme.fontFamily)};
font-family: ${escapeHtml(themeConfig.theme.fontFamily)};
background-color: #121212;
background-color: #121212;
color: #E0E0E0;
color: #E0E0E0;
display: flex;
display: flex;
justify-content: center;
justify-content: center;
align-items: center;
align-items: center;
min-height: 100vh;
min-height: 100vh;
margin: 0;
margin: 0;
padding: 20px;
padding: 20px;
flex-direction: column;
flex-direction: column;
gap: 20px;
gap: 20px;
}
}
.container {
.container {
max-width: 800px;
max-width: 800px;
padding: 40px;
padding: 40px;
border-radius: 10px;
border-radius: 10px;
background-color: #1E1E1E;
background-color: #1E1E1E;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
text-align: center;
text-align: center;
width: 100%;
width: 100%;
}
}
.form-container {
.form-container {
max-width: 800px;
max-width: 800px;
padding: 20px;
padding: 20px;
border-radius: 10px;
border-radius: 10px;
background-color: #1E1E1E;
background-color: #1E1E1E;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
width: 100%;
width: 100%;
}
}
h1 {
h1 {
color: ${escapeHtml(themeConfig.theme.primaryColor)};
color: ${escapeHtml(themeConfig.theme.primaryColor)};
font-size: 2.5rem;
font-size: 2.5rem;
margin-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
}
p {
p {
font-size: ${escapeHtml(themeConfig.theme.fontSize)};
font-size: ${escapeHtml(themeConfig.theme.fontSize)};
line-height: 1.6;
line-height: 1.6;
}
}
a {
a {
color: ${escapeHtml(themeConfig.theme.primaryColor)};
color: ${escapeHtml(themeConfig.theme.primaryColor)};
text-decoration: none;
text-decoration: none;
font-weight: bold;
font-weight: bold;
transition: color 0.3s ease;
transition: color 0.3s ease;
}
}
a:hover {
a:hover {
text-decoration: underline;
text-decoration: underline;
color: ${escapeHtml(themeConfig.theme.secondaryColor)};
color: ${escapeHtml(themeConfig.theme.secondaryColor)};
}
}
pre {
pre {
background-color: #000000;
background-color: #000000;
color: #00FF00;
color: #00FF00;
padding: 20px;
padding: 20px;
border-radius: 8px;
border-radius: 8px;
border: 1px solid ${escapeHtml(themeConfig.theme.secondaryColor)};
border: 1px solid ${escapeHtml(themeConfig.theme.secondaryColor)};
overflow-x: auto;
overflow-x: auto;
text-align: left;
text-align: left;
}
}
form {
form {
display: flex;
display: flex;
flex-direction: column;
flex-direction: column;
gap: 15px;
gap: 15px;
text-align: left;
text-align: left;
}
}
label {
label {
font-weight: bold;
font-weight: bold;
color: #E0E0E0;
color: #E0E0E0;
}
}
input[type="text"], input[type="password"], input[type="color"] {
input[type="text"], input[type="password"], input[type="color"] {
width: 100%;
width: 100%;
padding: 8px;
padding: 8px;
border-radius: 5px;
border-radius: 5px;
border: 1px solid ${escapeHtml(themeConfig.theme.primaryColor)};
border: 1px solid ${escapeHtml(themeConfig.theme.primaryColor)};
background-color: #2D2D2D;
background-color: #2D2D2D;
color: #E0E0E0;
color: #E0E0E0;
box-sizing: border-box;
box-sizing: border-box;
}
}
input[type="color"] {
input[type="color"] {
padding: 0;
padding: 0;
height: 40px;
height: 40px;
}
}
button {
button {
background-color: ${escapeHtml(themeConfig.theme.primaryColor)};
background-color: ${escapeHtml(themeConfig.theme.primaryColor)};
color: #fff;
color: #fff;
border: none;
border: none;
padding: 12px 20px;
padding: 12px 20px;
border-radius: 5px;
border-radius: 5px;
cursor: pointer;
cursor: pointer;
font-size: 1rem;
font-size: 1rem;
font-weight: bold;
font-weight: bold;
transition: background-color 0.3s ease;
transition: background-color 0.3s ease;
}
}
button:hover {
button:hover {
background-color: ${escapeHtml(themeConfig.theme.secondaryColor)};
background-color: ${escapeHtml(themeConfig.theme.secondaryColor)};
}
}
.error-message {
.error-message {
color: #FF6B6B;
color: #FF6B6B;
font-size: 0.9rem;
font-size: 0.9rem;
text-align: center;
text-align: center;
}
}
</style>
</style>
</head>
</head>
<body>
<body>
${pageBody}
${pageBody}
</body>
</body>
</html>
</html>
`;
`;
};
};


// 11. Registration Routes
// 11. Registration Routes
app.get('/register', (req, res) => {
app.get('/register', (req, res) => {
const errorMessage = req.session.errorMessage;
const errorMessage = req.session.errorMessage;
req.session.errorMessage = null; // Clear the error message after displaying it
req.session.errorMessage = null; // Clear the error message after displaying it
const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : '';
const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : '';


const pageBody = `
const pageBody = `
<div class="container">
<div class="container">
<h1>Register</h1>
<h1>Register</h1>
${errorHtml}
${errorHtml}
<form action="/register" method="POST">
<form action="/register" method="POST">
<input type="text" name="username" placeholder="Username" required><br><br>
<input type="text" name="username" placeholder="Username" required><br><br>
<input type="password" name="password" placeholder="Password" required><br><br>
<input type="password" name="password" placeholder="Password" required><br><br>
<button type="submit">Register</button>
<button type="submit">Register</button>
</form>
</form>
<p>Already have an account? <a href="/login">Login here</a></p>
<p>Already have an account? <a href="/login">Login here</a></p>
</div>
</div>
`;
`;
res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Register'));
res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Register'));
});
});


app.post('/register', (req, res) => {
app.post('/register', (req, res) => {
const { username, password } = req.body;
const { username, password } = req.body;
if (users[username]) {
if (users[username]) {
req.session.errorMessage = 'User already exists!';
req.session.errorMessage = 'User already exists!';
return res.redirect('/register');
return res.redirect('/register');
}
}
// Storing the password in plaintext for the CTF scenario.
// Storing the password in plaintext for the CTF scenario.
// DO NOT do this in a real application!
// DO NOT do this in a real application!
users[username] = {
users[username] = {
password: password,
password: password,
isAdmin: false,
themeConfig: {
themeConfig: {
theme: {
theme: {
primaryColor: '#6200EE',
primaryColor: '#6200EE',
secondaryColor: '#03DAC6',
secondaryColor: '#03DAC6',
fontSize: '16px',
fontSize: '16px',
fontFamily: 'Roboto, sans-serif'
fontFamily: 'Roboto, sans-serif'
}
}
}
}
};
};
req.session.userId = username;
req.session.userId = username;
res.redirect('/');
res.redirect('/');
});
});


// 12. Login Routes
// 12. Login Routes
app.get('/login', (req, res) => {
app.get('/login', (req, res) => {
const errorMessage = req.session.errorMessage;
const errorMessage = req.session.errorMessage;
req.session.errorMessage = null; // Clear the error message after displaying it
req.session.errorMessage = null; // Clear the error message after displaying it
const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : '';
const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : '';


const pageBody = `
const pageBody = `
<div class="container">
<div class="container">
<h1>Login</h1>
<h1>Login</h1>
${errorHtml}
${errorHtml}
<form action="/login" method="POST">
<form action="/login" method="POST">
<input type="text" name="username" placeholder="Username" required><br><br>
<input type="text" name="username" placeholder="Username" required><br><br>
<input type="password" name="password" placeholder="Password" required><br><br>
<input type="password" name="password" placeholder="Password" required><br><br>
<button type="submit">Login</button>
<button type="submit">Login</button>
</form>
</form>
<p>Don't have an account? <a href="/register">Register here</a></p>
<p>Don't have an account? <a href="/register">Register here</a></p>
</div>
</div>
`;
`;
res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Login'));
res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Login'));
});
});


app.post('/login', (req, res) => {
app.post('/login', (req, res) => {
const { username, password } = req.body;
const { username, password } = req.body;
const user = users[username];
const user = users[username];


// Comparing the plaintext password for the CTF scenario.
// Comparing the plaintext password for the CTF scenario.
// DO NOT do this in a real application!
// DO NOT do this in a real application!
if (user && user.password === password) {
if (user && user.password === password) {
req.session.userId = username;
req.session.userId = username;
res.redirect('/');
res.redirect('/');
} else {
} else {
req.session.errorMessage = 'Invalid username or password';
req.session.errorMessage = 'Invalid username or password';
res.redirect('/login');
res.redirect('/login');
}
}
});
});


// 13. Logout Route
// 13. Logout Route
app.get('/logout', (req, res) => {
app.get('/logout', (req, res) => {
req.session.destroy(err => {
req.session.destroy(err => {
if (err) {
if (err) {
return res.status(500).send('Could not log out.');
return res.status(500).send('Could not log out.');
}
}
res.redirect('/login');
res.redirect('/login');
});
});
});
});


// 14. Define the root endpoint (protected)
// 14. Define the root endpoint (protected)
app.get('/', isAuthenticated, (req, res) => {
app.get('/', isAuthenticated, (req, res) => {
const user = users[req.session.userId];
const user = users[req.session.userId];
if (!user) {
if (!user) {
return res.redirect('/login');
return res.redirect('/login');
}
}
const themeConfig = user.themeConfig;
const themeConfig = user.themeConfig;
const pageBody = `
const pageBody = `
<div class="container">
<div class="container">
<h1>Welcome, ${escapeHtml(req.session.userId)}!</h1>
<h1>Welcome, ${escapeHtml(req.session.userId)}!</h1>
<p>Current Theme Configuration:</p>
<p>Current Theme Configuration:</p>
<pre>${JSON.stringify(themeConfig, null, 2)}</pre>
<pre>${escapeHtml(JSON.stringify(themeConfig, null, 2))}</pre>
<p><a href="/logout">Logout</a></p>
<p><a href="/logout">Logout</a></p>
</div>
</div>


<div class="form-container">
<div class="form-container">
<h2>Customize Theme</h2>
<h2>Customize Theme</h2>
<form action="/theme" method="GET">
<form action="/theme" method="GET">
<label for="primaryColor">Primary Color:</label>
<label for="primaryColor">Primary Color:</label>
<input type="color" id="primaryColor" name="theme.primaryColor" value="${escapeHtml(themeConfig.theme.primaryColor)}">
<input type="color" id="primaryColor" name="theme.primaryColor" value="${escapeHtml(themeConfig.theme.primaryColor)}">


<label for="secondaryColor">Secondary Color:</label>
<label for="secondaryColor">Secondary Color:</label>
<input type="color" id="secondaryColor" name="theme.secondaryColor" value="${escapeHtml(themeConfig.theme.secondaryColor)}">
<input type="color" id="secondaryColor" name="theme.secondaryColor" value="${escapeHtml(themeConfig.theme.secondaryColor)}">


<label for="fontSize">Font Size (e.g., '16px'):</label>
<label for="fontSize">Font Size (e.g., '16px'):</label>
<input type="text" id="fontSize" name="theme.fontSize" value="${escapeHtml(themeConfig.theme.fontSize)}">
<input type="text" id="fontSize" name="theme.fontSize" value="${escapeHtml(themeConfig.theme.fontSize)}">


<label for="fontFamily">Font Family (e.g., 'Roboto, sans-serif'):</label>
<label for="fontFamily">Font Family (e.g., 'Roboto, sans-serif'):</label>
<input type="text" id="fontFamily" name="theme.fontFamily" value="${escapeHtml(themeConfig.theme.fontFamily)}">
<input type="text" id="fontFamily" name="theme.fontFamily" value="${escapeHtml(themeConfig.theme.fontFamily)}">
<button type="submit">Update Theme</button>
<button type="submit">Update Theme</button>
</form>
</form>
</div>
</div>
`;
`;
res.send(generateThemedPage(pageBody, themeConfig));
res.send(generateThemedPage(pageBody, themeConfig));
});
});




// 15. Define the `/theme` endpoint (protected)
// 15. Define the `/theme` endpoint (protected)
app.get('/theme', isAuthenticated, (req, res) => {
app.get('/theme', isAuthenticated, (req, res) => {
const user = users[req.session.userId];
const user = users[req.session.userId];
if (!user) {
if (!user) {
// This case should be handled by isAuthenticated middleware, but is here as a fallback
// This case should be handled by isAuthenticated middleware, but is here as a fallback
return res.redirect('/login');
return res.redirect('/login');
}
}


// Parse the query string into a nested object
// Parse the query string into a nested object
const queryString = req.url.split('?')[1] || '';
const queryString = req.url.split('?')[1] || '';
const parsedUpdates = parseQueryParams(queryString);
const parsedUpdates = parseQueryParams(queryString);


// If there are updates, merge them into the existing config.
// If there are updates, merge them into the existing config.
if (Object.keys(parsedUpdates).length > 0) {
if (Object.keys(parsedUpdates).length > 0) {
// Merge the parsed updates into the user's theme config.
// Merge the parsed updates into the user's theme config.
user.themeConfig = deepMerge(user.themeConfig, parsedUpdates);
user.themeConfig = deepMerge(user.themeConfig, parsedUpdates);
}
}


// Redirect the user back to the home page to see the updated theme.
// Redirect the user back to the home page to see the updated theme.
res.redirect('/');
res.redirect('/');
});
});


// 15. Define the `/flag` endpoint (protected)
app.get('/flag', isAuthenticated, (req, res, next)=>{
if(users[req.session.userId].isAdmin == true){
return res.end(FLAG);
}
return res.end("Not admin :(");
});



// 16. Start the Express server
// 16. Start the Express server
app.listen(PORT, () => {
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`);
console.log(`Server is running at http://localhost:${PORT}`);
console.log('Please register or login at http://localhost:3000/register or http://localhost:3000/login');
console.log('Please register or login at http://localhost:3000/register or http://localhost:3000/login');
});
});