DirtOil
A flower-seed and garden-supply shop. It writes up its own growing guides and routes every guide click through a tidy handoff page that runs entirely in your browser.
The Scenario
DirtOil is a small Vermont seed house that trial-grows its own stock and writes up the growing guides on its own site. It sends every guide click through a handoff page so it can see which guides get read. After a tampered link once bounced a customer off to a lookalike checkout, the owner had a developer add a check so the handoff only ever opens the shop's own guide pages. He wrote it over a lunch break and was pleased with how tidy it came out.
Challenge Intel
Synopsis
DOM-based open redirect that escalates to DOM XSS. /go.php reads the destination from location.search and assigns it to location.href after a substring-only allowlist (the destination must contain '/guide.php'), so a javascript: URI carrying that string slips through.
What It Is
The handoff page /go.php takes the destination from ?to= and, in the bundled /static/redirect.js, validates it with ALLOWED.some(a => to.includes(a)) where ALLOWED is ['/guide.php'], a plain substring test with no scheme check and no URL parsing. On a pass it runs location.href = to. The PHP page never reads ?to=, so the whole flaw is client-side. Because the check only looks for the substring '/guide.php' anywhere in the string, a javascript: URI that contains it passes and is assigned to location.href, which executes it. A Content-Security-Policy-Report-Only header on every page (script-src 'self', no unsafe-inline) reports the inline execution to /__csp-report.php, which marks the session solved, and /__status.php then returns the flag. The javascript: expression must evaluate to undefined, or the browser replaces the document and the in-page report is lost, so the canonical payload is /go.php?to=javascript:void(alert(document.domain))//guide.php (a bare alert also works, since alert returns undefined). The same substring flaw is a plain open redirect too (?to=//attacker.example/guide.php passes and bounces off-site), but an off-site landing cannot be self-detected in this challenge. The correct fix is to parse the URL, confirm it is same-origin, and check the path properly rather than substring-matching it.
Who It's For
Players who know reflected and stored XSS and want to see an open redirect that is really XSS because the navigation sink never checks the scheme.
Skills You'll Practice
- Tracing a destination from location.search into a location.href navigation sink
- Recognising that a substring path check is not real URL validation
- Escalating an open redirect to script execution with a javascript: URI
- Keeping a javascript: payload in-document with void or an undefined result so the report fires
What You'll Gain
- Confidence that location.href and location.assign sinks are XSS when the scheme goes unchecked
- The open-redirect-to-XSS boundary, and why substring-matching a path is not enough