fix - Writeup¶
I solved this challenge by abusing the internal admin page as a cross-origin query gadget and then pivoting into Solr's flag collection through the unsafely concatenated search query.
Overview¶
The challenge had three moving parts:
- a public web app on port
5003 - an internal admin app on port
8080 - a headless bot that visits attacker-controlled URLs submitted through
/api/report
At first glance, the intended bug looked straightforward: get the bot to load my page, steal the admin token from admin.html, and call the internal admin API with a malicious Solr query. The tricky part was getting that to work in a modern browser from a public origin.
The final solve was:
- I hosted a public page.
- That page created a sandboxed helper iframe with
allow-popups. - The helper opened
http://web:8080/admin.htmlin a popup. - Because the popup inherited the sandbox, its origin serialized as
"null". - The admin page accepted
postMessagefrom that popup because it only comparedevent.origintowindow.origin, and both were"null". - I used the admin page to query Solr with
*:*&shards.info=truefirst, discovered the real internal Solr host, then queried theflagcollection with*:*&shards=<host>/solr/flag&fl=flag. - I exfiltrated the result back to my server.
Vulnerability Analysis¶
1. The internal admin page was reachable from the bot¶
The app exposes /admin.html only on the internal port, but the bot browser runs inside the same Docker network, so it can access:
http://web:8080/admin.html
That alone would not be enough, but the page also embeds the admin token in JavaScript.
Relevant file:
web/templates/admin.html
2. The admin page trusted postMessage from "null"¶
The admin page contained this logic:
window.addEventListener("message", async (event) => {
if (event.origin !== window.origin) {
return;
}
...
});
That check is weak. If I load the page inside a sandboxed browsing context, the origin becomes opaque and serializes to "null". A second sandboxed context also serializes to "null", so the string comparison passes even though this is not a real same-origin relationship.
This became the core primitive of the final exploit.
3. The admin API concatenated q directly into the Solr URL¶
The internal admin endpoint did not safely structure Solr parameters. It took the user-supplied q and appended it directly to the backend Solr request. It only blocked the substring collection, which was not enough.
Relevant file:
web/server.js
That meant I could inject extra Solr parameters inside q, for example:
4. Solr exposed the shard address through shards.info=true¶
Instead of brute-forcing Docker bridge ranges, I first queried:
The response included the real shard address, so I could derive the exact internal Solr host dynamically and then query the flag collection reliably.
This made the exploit much more robust remotely.
What Did Not Work Reliably¶
Before landing on the popup route, I tried several variants:
- direct
fetch("http://web:8080/...")from a public page - a public page embedding
admin.htmldirectly in a sandboxed iframe - HTTPS plus
targetAddressSpace: "local" - popup retargeting to a
javascript:URL
The first three ran into modern Chrome private-network and mixed-content restrictions. The javascript: popup retarget idea failed with a SecurityError.
The sandboxed popup route was the one that survived both local and public-HTTPS rehearsal.
Final Exploit Strategy¶
I ended up serving a page equivalent to index.html.
The key logic was:
- Create a hidden sandboxed helper iframe:
- From inside that helper, call:
-
Relay queries from the parent to the popup with
postMessage. -
Wait for the popup to answer with:
- Parse the first response from
*:*&shards.info=true, extract the shard host, then send:
- Leak the resulting JSON back to my server with an
Imagerequest.
Why the Popup Route Worked¶
The important detail is that I no longer needed a public page to directly fetch a private-network URL. That was the part modern Chrome was blocking.
Instead, I used:
- top-level popup navigation to
http://web:8080/admin.html - sandbox inheritance to keep the popup origin opaque
- the admin page's own JavaScript to perform the authenticated internal
fetch
So I turned the admin page itself into the query gadget.
Validation¶
I validated the exploit in three stages:
- Local bot + private local page
- Local bot + public HTTPS tunnel
- Remote bot + public HTTPS tunnel
The second validation mattered the most because it reproduced the public-origin browser conditions that had broken the earlier approaches.
For the public tunnel, I exposed my local exploit server with:
Then I submitted the returned HTTPS URL to the report endpoint.
Reproduction¶
Start the exploit server¶
Expose it publicly¶
Submit to the challenge¶
curl -X POST 'http://TARGET/api/report' \
-H 'Content-Type: application/json' \
--data '{"url":"https://YOUR-TUNNEL.lhr.life/"}'
Expected leak stages¶
On my server, I expected to see:
helper_loadedpopup_openpopup_from_admindiscoveredflag
Takeaways¶
The challenge was not just about spotting a backend query-injection bug. The real difficulty was finding a browser delivery primitive that still worked under current Chrome behavior.
The successful chain combined:
- weak
postMessageorigin validation - a token embedded in internal HTML
- Solr parameter injection through
q - shard discovery through
shards.info=true - and a sandboxed popup to preserve the
"null"origin trick
That combination was enough to turn the internal admin page into an authenticated proxy for reading the hidden flag collection.