Conversation
Assignment8.vote() denylisted only the literal string "GET" via
request.getMethod().equals("GET") and let every other HTTP verb fall
through to the privileged branch that records a vote and returns the
flag in an X-FlagController response header. Since the WebGoat UI only
ever calls this endpoint with GET (challenge8.js issues $.get(...)),
any other verb - POST, PUT, HEAD, etc. - reached the "authenticated"
branch despite there being no actual authentication check at all,
letting an attacker who simply changes the request method obtain the
flag.
Replace the denylist with a fail-closed default: since there is no
real login/session check backing this endpoint, no verb should reach
the privileged branch, so every request (regardless of method) now
gets the same not-logged-in response that GET already returned. The
legitimate feature is unaffected because the only real caller
(challenge8.js) always issues GET requests, which behave identically
to before.
Closes Challenge-81-JWT-Voting-Challenge (A01 Broken Access Control).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
🏆 WebGoat — CTF Patch Score1 / 69 challenges patched
Commit: 🎉 Your result is on the leaderboard — see where you rank! 🏆 |
The CI scorer image (eclipse-temurin:25-jdk-noble) fails to compile with lombok 1.18.36 pinned in pom.xml: 'Fatal error compiling: java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTag :: UNKNOWN'. Lombok 1.18.36 predates JDK 25 compiler-internals support; 1.18.46 fixes it. Build-tooling-only change, no runtime/functional code touched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… 500) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
Assignment8.vote()(Challenge 8 – star rating / voting) tried to gate the privileged branch (record a vote and return the flag in anX-FlagControllerresponse header) with a denylist:The handler is mapped with
@GetMapping, so Spring MVC rejectsPOST/PUT/etc. with a 405 before the method body ever runs — butHEADis implicitly accepted on a@GetMappingroute (HTTP defines HEAD as "GET without a body"), sorequest.getMethod()returns"HEAD", which is never equal to the literal string"GET". That let an attacker reach the privileged branch with a singleHEADrequest and read the flag straight out of the response header, without any real authentication check ever being performed.Fix
Replaced the denylist with a fail-closed default: since this endpoint has no real login/session check behind it at all, no HTTP verb should reach the privileged branch. Every request now gets the same "you need to login first" response that
GETalready returned, regardless of method.Verification (local, WSL)
Built and ran the app locally (
java -cp target/classes:<deps> org.owasp.webgoat.server.StartWebGoat), registered a test user, and hit/WebGoat/challenge/8/vote/3directly:curl -X HEAD .../challenge/8/vote/3→200with anX-FlagControllerheader containing the flag.GET(the only method the real UI,challenge8.js, ever sends) → unchanged200JSON{"error":true,"message":"Sorry but you need to login first in order to vote"}.HEAD(verb-tampering exploit) →200, empty body, noX-FlagControllerheader.POST/PUT→ unaffected405 Method Not Allowed(already enforced by@GetMapping, unrelated to this fix).Legitimate feature (the rating widget on the Challenge 8 page) is unaffected since it only ever issues
GETrequests, whose response is byte-for-byte identical to before.🤖 Generated with Claude Code