A sophisticated, AI-resistant CTF puzzle that requires players to follow a complex multi-stage sequence to obtain the real flag. The visible MD5 hash is a decoy designed to frustrate automated solvers.
This puzzle implements a chained sequence that must be completed in order:
- Fragment Assembly - Extract and order Base64 fragments from page meta tags
- VM Execution - Run a JavaScript micro-VM to compute the correct fragment permutation
- Handshake - Submit the computed order to the server
- Unlock - Provide a proof derived from the nonce using local computation
- Blob Download - Retrieve an encrypted blob from the server
- Local Decryption - Decrypt the blob using the ephemeral key
- Final Claim - Submit the final proof to claim the flag
- Vanilla JavaScript - No external dependencies
- Meta Tag Fragments - Base64-encoded fragments in HTML meta tags
- Ordering VM - JavaScript micro-VM that computes fragment permutation
- Progressive Hints - Three levels of hints with increasing penalties
- Decoy MD5 - Visible hash that always fails server verification
- Express.js - Minimal Node.js server
- Session Management - Short TTL tokens with automatic cleanup
- Rate Limiting - IP-based rate limiting to prevent abuse
- AES Encryption - Encrypted blob that requires ephemeral key
- HMAC Verification - Server-side proof verification for all steps
- Comprehensive Logging - All requests logged with IP, UA, and timestamps
Set these environment variables before starting the server:
# Server configuration
SERVER_SALT=s3_hash_server_salt_2024
MASTER_ENCRYPTION_KEY=s3_hash_master_key_32_bytes_long_2024!
# Team flags (never commit these!)
FLAG_TEAM_1=flag{s3_hash_multi_stage_complete}
FLAG_TEAM_2=flag{another_team_flag}
FLAG_TEAM_3=flag{yet_another_team_flag}
-
Install dependencies:
npm install
-
Set environment variables:
export SERVER_SALT="your_secret_salt_here" export MASTER_ENCRYPTION_KEY="your_32_byte_key_here" export FLAG_TEAM_1="flag{your_team_flag_here}"
-
Start the server:
npm start
-
Access the puzzle: Open
http://localhost:3000
in your browser
Players must find the Base64 fragments in the HTML meta tags:
<!-- MARKER: S3_HASH_FRAGMENTS -->
<meta name="frag-0" content="VGhlIEZsb3dlcnM=" />
<meta name="frag-1" content="b2YgVW5pdHk=" />
<meta name="frag-2" content="QnVpbGRpbmc=" />
<meta name="frag-3" content="VGhlIFVuaXZlcnNl" />
Run the ordering VM to compute the correct permutation:
// Command: compute
// Output: computed order: [3,1,0,2]
session # Get session token
handshake 3,1,0,2 # Submit correct order
# Server returns nonce if order is correct
unlock <proof> # Submit proof derived from nonce
# Proof = HMAC_SHA256(nonce + "vm_key", "local_transform")
blob # Download encrypted blob
decrypt <ephemeral_key> # Decrypt blob locally
claim <final_proof> # Submit final proof
# final_proof = HMAC_SHA256(SERVER_SALT, plaintext + team_id)
Obtain a session token (90s TTL)
{
"token": "abc123...",
"ttl": 90
}
Always returns the decoy MD5 (bait for crackers)
{
"hash": "e0466f61b8c0aaf5aa20bfa6919cda77",
"message": "MD5 revealed — but you will need more than a crack."
}
Submit candidate fragment order
{
"candidate_order": [3, 1, 0, 2]
}
Submit proof to unlock ephemeral key
{
"nonce": "abc123...",
"proof": "def456..."
}
Download encrypted blob (requires unlocked session)
Headers: X-Session-Token: <token>
Submit final proof to claim flag
{
"final_proof": "ghi789...",
"team_id": "team_1"
}
- Session Tokens - Short TTL prevents mass scraping
- Rate Limiting - 10 requests per minute per IP
- Encrypted Blob - Requires server-side unlock
- VM Execution - Static analysis won't reveal correct order
- Progressive Hints - Penalized hint system
- Decoy MD5 - Always fails, explicit rejection message
- Chained Dependencies - Each step requires previous completion
- Ephemeral Keys - One-time use tokens
- Server Verification - All proofs verified server-side
- Session Token: 90 seconds
- Nonce: 30 seconds
- Rate Limit Window: 60 seconds
- Ephemeral Key: Single use only
-
git grep -n "flag{"
shows zero client-side hits -
missionexploit()
without session shows decoy MD5 - Cracking decoy MD5 fails with explicit rejection
- VM computes correct order [3,1,0,2]
- Handshake accepts correct order, rejects incorrect
- Unlock requires valid proof
- Blob requires unlocked session
- Final claim returns flag for correct proof
# Test the complete flow
npm test
# Check for client-side flags
git grep -n "flag{"
# Verify decoy behavior
curl -X GET http://localhost:3000/mission
All requests are logged with:
- Timestamp
- HTTP method and path
- Client IP address
- User-Agent string
- Team ID (when available)
Special logging for:
- Decoy submissions (for post-CTF analysis)
- Failed handshake attempts
- Rate limit violations
- Successful flag claims
- Never commit flags - Use environment variables only
- Set strong secrets - Use cryptographically secure random values
- Monitor logs - Watch for abuse patterns
- Rate limiting - Adjust limits based on expected traffic
- HTTPS in production - Ensure all traffic is encrypted
- "invalid session token" - Session expired, get new one
- "incorrect fragment order" - Run VM computation first
- "invalid proof" - Check proof calculation algorithm
- "session not unlocked" - Complete unlock step first
- "rate limit exceeded" - Wait before retrying
Set DEBUG=1
environment variable for verbose logging.
hash-s3-6/
├── index.html # Frontend with meta fragments
├── script.js # Main client-side logic
├── ordering-vm.js # JavaScript micro-VM
├── styles.css # Terminal styling
├── server.js # Express server
├── package.json # Dependencies
└── README.md # This file
MIT License - Use responsibly for educational CTF purposes only.