Diff
checker
텍스트
텍스트
이미지
문서
Excel
폴더
Legal
Enterprise
데스크톱
요금제
로그인
데스크톱 앱 다운로드
텍스트 비교
두 텍스트 파일의 차이점을 찾아보세요
도구
기록
실시간 편집
공백 변경 숨기기
변경 없는 행 숨기기
줄바꿈 비활성화
레이아웃
나란히 보기
합쳐 보기
비교 단위
스마트
단어
글자
텍스트 스타일
모양 변경
구문 강조
언어 선택
제외
텍스트 변환
첫 변경으로
수정
Diffchecker Desktop
가장 안전하게 Diffchecker를 사용하는 방법. 데스크톱 앱을 사용하면 비교 데이터가 외부로 전송되지 않습니다!
데스크톱 앱 받기
Diff
생성일
작년
비교 결과 만료 없음
초기화
내보내기
공유
설명
2 삭제
행
총
삭제
글자
총
삭제
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
381 행
복사
24 추가
행
총
추가
글자
총
추가
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
399 행
복사
// 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, "<")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/"/g, """)
.replace(/'/g, "'");
.replace(/'/g, "'");
};
};
// 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');
});
});
복사
복사됨
복사
복사됨
저장된 비교 결과
원본
파일 열기
// A simple Express.js application to demonstrate user registration, login, // session handling, and safe rendering of user-controlled data. // // === WARNING: FOR CTF (CAPTURE THE FLAG) USE ONLY === // This code stores passwords in plaintext. This is a severe security vulnerability // 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. // 1. Import necessary libraries // Note: You will need to run `npm install express express-session` to use this code. // bcrypt has been removed as per the CTF request. const express = require('express'); const session = require('express-session'); const app = express(); const PORT = 3000; // 2. Middleware to parse request bodies (for form data) app.use(express.urlencoded({ extended: true })); // 3. Configure session middleware app.use(session({ secret: 'a_very_secure_secret_key_for_session', // In a real app, this should be an environment variable resave: false, saveUninitialized: true, cookie: { secure: false } // Set to true if using HTTPS })); // 4. In-memory user data store (simulating a database) // In a real application, you would use a database like MongoDB or PostgreSQL. // For this CTF scenario, passwords are stored in plaintext. const users = {}; // Stores { username: { password, userThemeConfig } } // 5. A simple function to safely escape HTML to prevent XSS attacks. const escapeHtml = (unsafe) => { if (typeof unsafe !== 'string') return unsafe; return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; // 6. A function to recursively merge objects const deepMerge = (target, source) => { for (const key in source) { if (source[key] instanceof Object && key in target) { Object.assign(source[key], deepMerge(target[key], source[key])); } } Object.assign(target || {}, source); return target; }; // 7. A function to parse a query string with dot-notation keys. const parseQueryParams = (queryString) => { if (typeof queryString !== 'string') { return {}; } const cleanString = queryString.startsWith('?') ? queryString.substring(1) : queryString; const params = new URLSearchParams(cleanString); const result = {}; for (const [key, value] of params.entries()) { const path = key.split('.'); let current = result; for (let i = 0; i < path.length; i++) { const part = path[i]; if (i === path.length - 1) { current[part] = value; } else { if (!current[part] || typeof current[part] !== 'object') { current[part] = {}; } current = current[part]; } } } return result; }; // 8. Authentication Middleware // This function checks if a user is logged in before allowing access to a route. const isAuthenticated = (req, res, next) => { if (req.session.userId) { next(); // User is authenticated, proceed to the next middleware/route handler } else { res.redirect('/login'); // User is not authenticated, redirect to login page } }; // 9. Default Theme Configuration const defaultThemeConfig = { theme: { primaryColor: '#8E24AA', // A nice shade of purple secondaryColor: '#FFC107', // An amber yellow fontSize: '18px', fontFamily: 'Arial, sans-serif' } }; // 10. Helper function to generate a styled HTML page const generateThemedPage = (pageBody, themeConfig, title = 'Theme Configuration App') => { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${escapeHtml(title)}</title> <style> body { font-family: ${escapeHtml(themeConfig.theme.fontFamily)}; background-color: #121212; color: #E0E0E0; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; padding: 20px; flex-direction: column; gap: 20px; } .container { max-width: 800px; padding: 40px; border-radius: 10px; background-color: #1E1E1E; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); text-align: center; width: 100%; } .form-container { max-width: 800px; padding: 20px; border-radius: 10px; background-color: #1E1E1E; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); width: 100%; } h1 { color: ${escapeHtml(themeConfig.theme.primaryColor)}; font-size: 2.5rem; margin-bottom: 0.5rem; } p { font-size: ${escapeHtml(themeConfig.theme.fontSize)}; line-height: 1.6; } a { color: ${escapeHtml(themeConfig.theme.primaryColor)}; text-decoration: none; font-weight: bold; transition: color 0.3s ease; } a:hover { text-decoration: underline; color: ${escapeHtml(themeConfig.theme.secondaryColor)}; } pre { background-color: #000000; color: #00FF00; padding: 20px; border-radius: 8px; border: 1px solid ${escapeHtml(themeConfig.theme.secondaryColor)}; overflow-x: auto; text-align: left; } form { display: flex; flex-direction: column; gap: 15px; text-align: left; } label { font-weight: bold; color: #E0E0E0; } input[type="text"], input[type="password"], input[type="color"] { width: 100%; padding: 8px; border-radius: 5px; border: 1px solid ${escapeHtml(themeConfig.theme.primaryColor)}; background-color: #2D2D2D; color: #E0E0E0; box-sizing: border-box; } input[type="color"] { padding: 0; height: 40px; } button { background-color: ${escapeHtml(themeConfig.theme.primaryColor)}; color: #fff; border: none; padding: 12px 20px; border-radius: 5px; cursor: pointer; font-size: 1rem; font-weight: bold; transition: background-color 0.3s ease; } button:hover { background-color: ${escapeHtml(themeConfig.theme.secondaryColor)}; } .error-message { color: #FF6B6B; font-size: 0.9rem; text-align: center; } </style> </head> <body> ${pageBody} </body> </html> `; }; // 11. Registration Routes app.get('/register', (req, res) => { const errorMessage = req.session.errorMessage; req.session.errorMessage = null; // Clear the error message after displaying it const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : ''; const pageBody = ` <div class="container"> <h1>Register</h1> ${errorHtml} <form action="/register" method="POST"> <input type="text" name="username" placeholder="Username" required><br><br> <input type="password" name="password" placeholder="Password" required><br><br> <button type="submit">Register</button> </form> <p>Already have an account? <a href="/login">Login here</a></p> </div> `; res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Register')); }); app.post('/register', (req, res) => { const { username, password } = req.body; if (users[username]) { req.session.errorMessage = 'User already exists!'; return res.redirect('/register'); } // Storing the password in plaintext for the CTF scenario. // DO NOT do this in a real application! users[username] = { password: password, themeConfig: { theme: { primaryColor: '#6200EE', secondaryColor: '#03DAC6', fontSize: '16px', fontFamily: 'Roboto, sans-serif' } } }; req.session.userId = username; res.redirect('/'); }); // 12. Login Routes app.get('/login', (req, res) => { const errorMessage = req.session.errorMessage; req.session.errorMessage = null; // Clear the error message after displaying it const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : ''; const pageBody = ` <div class="container"> <h1>Login</h1> ${errorHtml} <form action="/login" method="POST"> <input type="text" name="username" placeholder="Username" required><br><br> <input type="password" name="password" placeholder="Password" required><br><br> <button type="submit">Login</button> </form> <p>Don't have an account? <a href="/register">Register here</a></p> </div> `; res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Login')); }); app.post('/login', (req, res) => { const { username, password } = req.body; const user = users[username]; // Comparing the plaintext password for the CTF scenario. // DO NOT do this in a real application! if (user && user.password === password) { req.session.userId = username; res.redirect('/'); } else { req.session.errorMessage = 'Invalid username or password'; res.redirect('/login'); } }); // 13. Logout Route app.get('/logout', (req, res) => { req.session.destroy(err => { if (err) { return res.status(500).send('Could not log out.'); } res.redirect('/login'); }); }); // 14. Define the root endpoint (protected) app.get('/', isAuthenticated, (req, res) => { const user = users[req.session.userId]; if (!user) { return res.redirect('/login'); } const themeConfig = user.themeConfig; const pageBody = ` <div class="container"> <h1>Welcome, ${escapeHtml(req.session.userId)}!</h1> <p>Current Theme Configuration:</p> <pre>${JSON.stringify(themeConfig, null, 2)}</pre> <p><a href="/logout">Logout</a></p> </div> <div class="form-container"> <h2>Customize Theme</h2> <form action="/theme" method="GET"> <label for="primaryColor">Primary Color:</label> <input type="color" id="primaryColor" name="theme.primaryColor" value="${escapeHtml(themeConfig.theme.primaryColor)}"> <label for="secondaryColor">Secondary Color:</label> <input type="color" id="secondaryColor" name="theme.secondaryColor" value="${escapeHtml(themeConfig.theme.secondaryColor)}"> <label for="fontSize">Font Size (e.g., '16px'):</label> <input type="text" id="fontSize" name="theme.fontSize" value="${escapeHtml(themeConfig.theme.fontSize)}"> <label for="fontFamily">Font Family (e.g., 'Roboto, sans-serif'):</label> <input type="text" id="fontFamily" name="theme.fontFamily" value="${escapeHtml(themeConfig.theme.fontFamily)}"> <button type="submit">Update Theme</button> </form> </div> `; res.send(generateThemedPage(pageBody, themeConfig)); }); // 15. Define the `/theme` endpoint (protected) app.get('/theme', isAuthenticated, (req, res) => { const user = users[req.session.userId]; if (!user) { // This case should be handled by isAuthenticated middleware, but is here as a fallback return res.redirect('/login'); } // Parse the query string into a nested object const queryString = req.url.split('?')[1] || ''; const parsedUpdates = parseQueryParams(queryString); // If there are updates, merge them into the existing config. if (Object.keys(parsedUpdates).length > 0) { // Merge the parsed updates into the user's theme config. user.themeConfig = deepMerge(user.themeConfig, parsedUpdates); } // Redirect the user back to the home page to see the updated theme. res.redirect('/'); }); // 16. Start the Express server app.listen(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'); });
수정본
파일 열기
// A simple Express.js application to demonstrate user registration, login, // session handling, and safe rendering of user-controlled data. // // === WARNING: FOR CTF (CAPTURE THE FLAG) USE ONLY === // This code stores passwords in plaintext. This is a severe security vulnerability // 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. // 1. Import necessary libraries // Note: You will need to run `npm install express express-session` to use this code. // bcrypt has been removed as per the CTF request. const express = require('express'); const session = require('express-session'); const crypto = require('crypto'); const app = express(); 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) app.use(express.urlencoded({ extended: true })); // 3. Configure session middleware app.use(session({ secret: SECRET, resave: false, saveUninitialized: true, cookie: { secure: false } // Set to true if using HTTPS })); // 4. In-memory user data store (simulating a database) // In a real application, you would use a database like MongoDB or PostgreSQL. // For this CTF scenario, passwords are stored in plaintext. const users = {}; // Stores { username: { password, userThemeConfig, isAdmin } } // 5. A simple function to safely escape HTML to prevent XSS attacks. const escapeHtml = (unsafe) => { if (typeof unsafe !== 'string') return unsafe; return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; // 6. A function to recursively merge objects const deepMerge = (target, source) => { for (const key in source) { if (source[key] instanceof Object && key in target) { Object.assign(source[key], deepMerge(target[key], source[key])); } } Object.assign(target || {}, source); return target; }; // 7. A function to parse a query string with dot-notation keys. const parseQueryParams = (queryString) => { if (typeof queryString !== 'string') { return {}; } const cleanString = queryString.startsWith('?') ? queryString.substring(1) : queryString; const params = new URLSearchParams(cleanString); const result = {}; for (const [key, value] of params.entries()) { const path = key.split('.'); let current = result; for (let i = 0; i < path.length; i++) { let part = path[i]; // Protect against Prototype Pollution vulnerability if(['__proto__', 'prototype', 'constructor'].includes(part)){ part = '__unsafe$' + part; } if (i === path.length - 1) { current[part] = value; } else { if (!current[part] || typeof current[part] !== 'object') { current[part] = {}; } current = current[part]; } } } return result; }; // 8. Authentication Middleware // This function checks if a user is logged in before allowing access to a route. const isAuthenticated = (req, res, next) => { if (req.session.userId) { next(); // User is authenticated, proceed to the next middleware/route handler } else { res.redirect('/login'); // User is not authenticated, redirect to login page } }; // 9. Default Theme Configuration const defaultThemeConfig = { theme: { primaryColor: '#8E24AA', // A nice shade of purple secondaryColor: '#FFC107', // An amber yellow fontSize: '18px', fontFamily: 'Arial, sans-serif' } }; // 10. Helper function to generate a styled HTML page const generateThemedPage = (pageBody, themeConfig, title = 'Theme Configuration App') => { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${escapeHtml(title)}</title> <style> body { font-family: ${escapeHtml(themeConfig.theme.fontFamily)}; background-color: #121212; color: #E0E0E0; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; padding: 20px; flex-direction: column; gap: 20px; } .container { max-width: 800px; padding: 40px; border-radius: 10px; background-color: #1E1E1E; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); text-align: center; width: 100%; } .form-container { max-width: 800px; padding: 20px; border-radius: 10px; background-color: #1E1E1E; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); width: 100%; } h1 { color: ${escapeHtml(themeConfig.theme.primaryColor)}; font-size: 2.5rem; margin-bottom: 0.5rem; } p { font-size: ${escapeHtml(themeConfig.theme.fontSize)}; line-height: 1.6; } a { color: ${escapeHtml(themeConfig.theme.primaryColor)}; text-decoration: none; font-weight: bold; transition: color 0.3s ease; } a:hover { text-decoration: underline; color: ${escapeHtml(themeConfig.theme.secondaryColor)}; } pre { background-color: #000000; color: #00FF00; padding: 20px; border-radius: 8px; border: 1px solid ${escapeHtml(themeConfig.theme.secondaryColor)}; overflow-x: auto; text-align: left; } form { display: flex; flex-direction: column; gap: 15px; text-align: left; } label { font-weight: bold; color: #E0E0E0; } input[type="text"], input[type="password"], input[type="color"] { width: 100%; padding: 8px; border-radius: 5px; border: 1px solid ${escapeHtml(themeConfig.theme.primaryColor)}; background-color: #2D2D2D; color: #E0E0E0; box-sizing: border-box; } input[type="color"] { padding: 0; height: 40px; } button { background-color: ${escapeHtml(themeConfig.theme.primaryColor)}; color: #fff; border: none; padding: 12px 20px; border-radius: 5px; cursor: pointer; font-size: 1rem; font-weight: bold; transition: background-color 0.3s ease; } button:hover { background-color: ${escapeHtml(themeConfig.theme.secondaryColor)}; } .error-message { color: #FF6B6B; font-size: 0.9rem; text-align: center; } </style> </head> <body> ${pageBody} </body> </html> `; }; // 11. Registration Routes app.get('/register', (req, res) => { const errorMessage = req.session.errorMessage; req.session.errorMessage = null; // Clear the error message after displaying it const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : ''; const pageBody = ` <div class="container"> <h1>Register</h1> ${errorHtml} <form action="/register" method="POST"> <input type="text" name="username" placeholder="Username" required><br><br> <input type="password" name="password" placeholder="Password" required><br><br> <button type="submit">Register</button> </form> <p>Already have an account? <a href="/login">Login here</a></p> </div> `; res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Register')); }); app.post('/register', (req, res) => { const { username, password } = req.body; if (users[username]) { req.session.errorMessage = 'User already exists!'; return res.redirect('/register'); } // Storing the password in plaintext for the CTF scenario. // DO NOT do this in a real application! users[username] = { password: password, isAdmin: false, themeConfig: { theme: { primaryColor: '#6200EE', secondaryColor: '#03DAC6', fontSize: '16px', fontFamily: 'Roboto, sans-serif' } } }; req.session.userId = username; res.redirect('/'); }); // 12. Login Routes app.get('/login', (req, res) => { const errorMessage = req.session.errorMessage; req.session.errorMessage = null; // Clear the error message after displaying it const errorHtml = errorMessage ? `<p class="error-message">${escapeHtml(errorMessage)}</p>` : ''; const pageBody = ` <div class="container"> <h1>Login</h1> ${errorHtml} <form action="/login" method="POST"> <input type="text" name="username" placeholder="Username" required><br><br> <input type="password" name="password" placeholder="Password" required><br><br> <button type="submit">Login</button> </form> <p>Don't have an account? <a href="/register">Register here</a></p> </div> `; res.send(generateThemedPage(pageBody, defaultThemeConfig, 'Login')); }); app.post('/login', (req, res) => { const { username, password } = req.body; const user = users[username]; // Comparing the plaintext password for the CTF scenario. // DO NOT do this in a real application! if (user && user.password === password) { req.session.userId = username; res.redirect('/'); } else { req.session.errorMessage = 'Invalid username or password'; res.redirect('/login'); } }); // 13. Logout Route app.get('/logout', (req, res) => { req.session.destroy(err => { if (err) { return res.status(500).send('Could not log out.'); } res.redirect('/login'); }); }); // 14. Define the root endpoint (protected) app.get('/', isAuthenticated, (req, res) => { const user = users[req.session.userId]; if (!user) { return res.redirect('/login'); } const themeConfig = user.themeConfig; const pageBody = ` <div class="container"> <h1>Welcome, ${escapeHtml(req.session.userId)}!</h1> <p>Current Theme Configuration:</p> <pre>${escapeHtml(JSON.stringify(themeConfig, null, 2))}</pre> <p><a href="/logout">Logout</a></p> </div> <div class="form-container"> <h2>Customize Theme</h2> <form action="/theme" method="GET"> <label for="primaryColor">Primary Color:</label> <input type="color" id="primaryColor" name="theme.primaryColor" value="${escapeHtml(themeConfig.theme.primaryColor)}"> <label for="secondaryColor">Secondary Color:</label> <input type="color" id="secondaryColor" name="theme.secondaryColor" value="${escapeHtml(themeConfig.theme.secondaryColor)}"> <label for="fontSize">Font Size (e.g., '16px'):</label> <input type="text" id="fontSize" name="theme.fontSize" value="${escapeHtml(themeConfig.theme.fontSize)}"> <label for="fontFamily">Font Family (e.g., 'Roboto, sans-serif'):</label> <input type="text" id="fontFamily" name="theme.fontFamily" value="${escapeHtml(themeConfig.theme.fontFamily)}"> <button type="submit">Update Theme</button> </form> </div> `; res.send(generateThemedPage(pageBody, themeConfig)); }); // 15. Define the `/theme` endpoint (protected) app.get('/theme', isAuthenticated, (req, res) => { const user = users[req.session.userId]; if (!user) { // This case should be handled by isAuthenticated middleware, but is here as a fallback return res.redirect('/login'); } // Parse the query string into a nested object const queryString = req.url.split('?')[1] || ''; const parsedUpdates = parseQueryParams(queryString); // If there are updates, merge them into the existing config. if (Object.keys(parsedUpdates).length > 0) { // Merge the parsed updates into the user's theme config. user.themeConfig = deepMerge(user.themeConfig, parsedUpdates); } // Redirect the user back to the home page to see the updated theme. 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 app.listen(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'); });
비교하기