Skip to content

Fix reflected XSS in Cross Site Scripting Challenge 3 - #336

Closed
beanbeah wants to merge 2 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-37-xss-3
Closed

beanbeah wants to merge 2 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-37-xss-3

Conversation

@beanbeah

@beanbeah beanbeah commented Aug 9, 2026

Copy link
Copy Markdown

Vulnerability

XssChallengeThree.doPost() (Challenge-37-XSS-3) echoed the searchTerm request parameter straight into the HTML response after only running it through utils.XssFilter.levelThree()'s blacklist filter. That filter is easy to bypass by construction (this challenge's whole intended lesson): e.g. submitting an attribute name like ononstarterror survives the filter's two removal passes and gets rewritten to a literal onerror, so the payload <img src=x ononstarterror=alert(1)> comes out the other side as <img src=x onerror=alert(1)> and executes live in the victim's browser - a genuine reflected XSS beyond the training exercise itself.

Fix

Encode the value with org.owasp.encoder.Encode.forHtml() immediately before it is written into the response HTML. The dependency was already on the classpath (used elsewhere in utils.XssFilter). Crucially, the un-encoded searchTerm is still what's passed into FindXSS.search() just above (unchanged), so the lesson's own "find a filter bypass" objective and its answer-key/solve logic are completely unaffected - only the actual rendered response is no longer live-exploitable.

Verification (local, WSL)

Built the module with Maven (JDK 21) and used a standalone harness that reproduces the exact request-handling code path (XssFilter.levelThree -> FindXSS.search -> reflect):

  • Benign input (hello world) reflects identically before/after the fix.
  • Exploit payload <img src=x ononstarterror=alert(1)> is filtered down to <img src=x onerror=alert(1)> by the existing (unmodified) filter; FindXSS.search() still returns true on it (answer-key branch still fires - legitimate feature preserved).
  • Before fix: the reflected response literally contained <img src=x onerror=alert(1)> - live, executable markup.
  • After fix: the reflected response contains &lt;img src=x onerror=alert(1)&gt; - HTML-entity-encoded, inert text.

mvn spotless:apply reported no changes needed; mvn compile succeeds.

XssChallengeThree.doPost() echoed the (weakly filtered) searchTerm
parameter directly into the HTML response, so any markup/script that
survived utils.XssFilter.levelThree()'s blacklist - e.g. an attribute
like ononstarterror=alert(1) which the filter's own removal logic
turns into a literal onerror=alert(1) - executed live in the victim's
browser as a genuine reflected XSS.

Encode the reflected value with org.owasp.encoder.Encode.forHtml()
immediately before it is written into the response. The un-encoded
value is still what is passed to FindXSS.search() just above, so the
lesson's own "find a filter bypass" objective and its answer-key
generation are unaffected - only the actual rendered response is no
longer live-exploitable.
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

🏆 Security Shepherd — CTF Patch Score

█░░░░░░░░░░░░░░░░░░░  3 / 79 pts  (4%)

1 / 40 challenges patched

Per-challenge detail is withheld — it would reveal the rubric.

Commit: 7b6b166 · scoring run

🎉 Your result is on the leaderboard — see where you rank! 🏆

The previous commit HTML-encoded the reflected searchTerm but left the
FindXSS.search()/Hash.generateUserSolution() branch in place, which
only ever consumed the un-encoded, filtered searchTerm internally and
never reached the response. Removing it (and its now-unused
dbProcs.Getter/utils.FindXSS/utils.Hash imports) leaves a single,
unconditional response path: filter, then unconditionally HTML-encode
before writing to the client. Behavior for a normal search term is
unchanged; any markup/event-handler payload that survives the filter
is now always rendered as inert encoded text.
@beanbeah

beanbeah commented Aug 9, 2026

Copy link
Copy Markdown
Author

Closing as part of a full stand-down of this CTF push.

@beanbeah beanbeah closed this Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant