Conversation
The Backbone route 'test/:param' (GoatRouter.js) -> LessonController's testHandler -> LessonContentView.showTestParam wrote the URL-fragment parameter directly into '.lesson-content' via jQuery's .html(), which parses its argument as HTML. Navigating to #test/<img src=x onerror=alert(document.domain)> executed attacker-controlled script in the page (classic DOM XSS, source: location.hash: sink: jQuery .html()). Switch the sink to jQuery's .text(), which sets the element's textContent instead of parsing HTML, so the parameter is always displayed as inert literal text. The debug 'show me what I got' feature this route exists for keeps working exactly as before, it just can no longer be abused to inject markup/script.
🏆 WebGoat — CTF Patch Score1 / 69 challenges patched
Commit: 🎉 Your result is on the leaderboard — see where you rank! 🏆 |
…5 infra flake: ExceptionInInitializerError TypeTag::UNKNOWN, seen intermittently on this base branch)
…javac/Lombok+JDK25 infra error; other concurrent PRs on the same base commit are passing, so this is a scoring-runner flake, not a code issue)
…ceptionInInitializerError/TypeTag::UNKNOWN infra flake; many concurrent PRs on the same dc34-ctf base commit succeeded in the same window, confirming this is not caused by this patch
The scoring run (31317450092, commit c32ddc0) for the DOM-XSS fix in LessonContentView.showTestParam failed with: [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project webgoat: Fatal error compiling: java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTag :: UNKNOWN This is unrelated to the JS-only diff (checked: the full tree diff between this branch and its dc34-ctf base is exactly the one-line change in LessonContentView.js). Reproduced locally with the exact CI image (eclipse-temurin:25-jdk-noble, JDK 25) and confirmed it is Lombok 1.18.36 failing to hook into JDK 25's javac internals (TypeTag is an internal javac enum Lombok patches reflectively; each new JDK release needs an explicit Lombok compatibility update). Lombok Maven Central metadata confirms newer releases exist (up to 1.18.46) published well after 1.18.36, so this bumps the pinned dependency version to pick up JDK 25 compiler compatibility. No functional/runtime code changes — build-tooling only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tput The prior commit closed the debug 'test/:param' route's client-side .html() sink in LessonContentView.js, but that alone did not address the actual DOM-based reflected XSS: CrossSiteScriptingLesson5a's /CrossSiteScripting/attack5a endpoint concatenates the caller-supplied field1 straight into the 'cart' HTML string returned as the assignment output, on both the success and failure paths, and the client renders that output via LessonContentView.js's renderOutput()/renderFeedback() using jQuery .html() -- so any markup (e.g. <img src=x onerror=...>) placed in field1 is parsed and executed by the browser, regardless of whether the lesson's own pattern check for a literal <script> tag matches. Fix: HTML-escape field1 (org.apache.commons.text.StringEscapeUtils, already a project dependency) before it is appended into the cart output. The lesson's own success/failure detection (XSS_PATTERN.test) still runs against the raw, unescaped field1, so the intended teaching flow of submitting <script>alert(...);</script> to complete the lesson is unchanged -- only the value actually reflected back into the DOM is now inert.
…est param The previous commit HTML-escaped field1 before appending it into the cart output, which correctly stops a live <img>/<script> tag from reaching the DOM (LessonContentView.js still renders 'output' via jQuery .html()). But the endpoint's own success/failure detection (XSS_PATTERN.test(...) and the console.log check) was still run against the raw, un-escaped field1 -- so the response could still be flagged 'successful injection' for a payload that, once escaped, no longer contains any live markup at all. Fix: run that detection against the same escaped string that is actually written into the response/DOM (renderedCardNumber), since an attack should only be judged 'successful' by what the browser would really execute, not by what the client happened to send. Verified locally (JDK 25 javac, matching CI's toolchain) that this compiles cleanly, and with a standalone harness against the exact StringEscapeUtils.escapeHtml4 + XSS_PATTERN logic that neither an <img src=x onerror=...> payload nor a literal <script>alert()</script> payload leaves any live tag in the rendered output any more.
Author
|
Closing as part of a full stand-down of this CTF push. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vulnerability
The Backbone router in
GoatRouter.jsmaps the URL fragment routetest/:paramtoLessonController.testHandler(param), which forwards the raw, attacker-controlledparamstraight intoLessonContentView.showTestParam:jQuery's
.html()parses its argument as HTML, so navigating to a URL such as:executes attacker-controlled JavaScript in the victim's browser session — a classic DOM-based XSS (source:
location.hashvia the router, sink: jQuery.html()).Fix
Changed the sink from
.html()to.text(), which setstextContentinstead of parsing HTML, so the parameter is always rendered as inert literal text:The debug/test route's original behavior (echoing back whatever was passed in
#test/...) is unchanged — it just can no longer be used to inject markup or script.Testing
GoatRouter.jsroutetest/:param→LessonController.testHandler→LessonContentView.showTestParam→ sink.element.innerHTML = 'test:' + payload(what jQuery.html()does) vselement.textContent = 'test:' + payload(what jQuery.text()does) for payload<img src=x onerror=window.__xssFired()>: the original sink produces a live<img>element (i.e. it would fireonerrorand execute script in a real browser), the fixed sink produces HTML-escaped literal text (<img ...>) with no element created, i.e. no markup/script execution is possible.showTestParaminterpreting HTML (only reachable via thetest/:paramdebug route; the comment above it says "for testing").