The challenge: Web app lets you upload files and have a headless Chrome bot visit them. The flag is encrypted with a random key, and the key is set as a cookie on the bot. There’s a /flag?secret=<guess> endpoint that returns the flag if you guess the secret correctly (uses Redis GETDEL so it’s consumed on use).
The CSP:
<span>default</span>-src <span>'self'</span> <span>'unsafe-inline'</span><br>script-src <span>'none'</span><br>
Also X-Content-Type-Options: nosniff and Chrome policy blocks all external URLs — only
https://web is allowed.
What I’ve tried so far:
- multipart/x-mixed-replace — uploaded a file with that content-type, part 1 innocent HTML, part 2 with <script> stealing the flag. Hoping Chrome doesn’t re-apply CSP to pushed parts. No luck.
- <object data="data:text/html;base64,...> — embedded JS payload in a data URL inside object tag. CSP still blocks it.
- <iframe srcdoc="<script>..."> — tried srcdoc iframe. Also blocked.
- SVG + foreignObject + data iframe — nested layers hoping to break CSP inheritance. Nope.
- CSS oracle — this actually works! Using body[secret^="XX"] { background-image: url(/flag?secret=consume) } I can get 1 bit per bot visit. But the secret changes every visit so I can’t binary search across visits.
What I’m stuck on:- Need to either get JS to execute despite script-src 'none' OR extract the full 32-char hex secret in a single bot visit using only CSS
- CSS can make GET requests via background-image but can’t concatenate attribute values into URLs
- /flag uses GETDEL so only ONE call can return the flag