Conversation
Every branch built against this base was failing to build/boot in CI with ExceptionInInitializerError: TypeTag :: UNKNOWN, because lombok 1.18.36 can't reflectively patch JDK 25's javac internals. Baking the fix into the fork's own dc34-ctf base so every future branch inherits it automatically instead of needing a manual per-branch cherry-pick. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…en endpoints
JWTRefreshEndpoint.checkout()/newToken() parsed the incoming JWT with
Jwts.parser().parse(token), which in jjwt accepts BOTH signed JWS tokens
and unsecured ("alg":"none") JWTs without verifying anything. An
attacker could therefore forge an unsigned token with claim
user=Tom and have it treated as authentic, bypassing the signature
check entirely (the classic alg:none JWT vulnerability). The old code
even had a dedicated success branch that rewarded exactly this forged
alg:none payload.
Fix: use Jwts.parser().parseClaimsJws(token) instead of parse(token) on
both endpoints. parseClaimsJws() requires the token to be a genuine JWS
(a cryptographically signed JWT) and throws JwtException/
UnsupportedJwtException for any unsecured/alg:none token, so a forged
token is rejected before its claims are ever trusted. Added a
JwtException catch in newToken() (parse() previously never threw for a
successfully-forged alg:none token, so no handling existed there) that
returns 401, mirroring the existing behavior in checkout(). Removed the
now-unreachable/incorrect success-for-alg-none branch in checkout().
Verified independently with a standalone jjwt harness (outside the
Spring test context) showing: (1) the old parse() call accepts a forged
alg:none token and extracts user=Tom, (2) parseClaimsJws() rejects the
identical forged token with UnsupportedJwtException, and (3) a
legitimately HS512-signed token for the same user is still accepted by
parseClaimsJws() unchanged - so the real login/refresh/checkout flow for
Tom and Jerry keeps working.
Updated JWTRefreshEndpointTest: renamed solutionWithAlgNone to
algNoneTokenShouldBeRejected and flipped its assertions to expect
lessonCompleted=false with the jwt-invalid-token feedback message,
since an unsigned alg:none token must never be treated as a solved
assignment.
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! 🏆 |
…Header,Claims>) CI compile failed: parseClaimsJws() returns Jws<Claims>, which is not assignable to a Jwt<Header, Claims> variable (Java generics are invariant, and Jws<T> extends Jwt<JwsHeader, T>, not Jwt<Header, T>). Declare the local as Jws<Claims> instead in both checkout() and newToken(); .getBody() is unchanged. Reproduced and confirmed this exact declaration compiles and behaves correctly against the project's pinned jjwt 0.9.1 with a standalone javac/java smoke test before pushing. 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
JWTRefreshEndpoint(/JWT/refresh/checkoutand/JWT/refresh/newToken) parsed the caller-supplied JWT withJwts.parser().parse(token). In jjwt,parse()accepts both cryptographically-signed JWS tokens and unsecured ("alg":"none") JWTs without verifying anything for the latter. An attacker could craft an unsigned token withuser=Tomin the claims and have it accepted as authentic, bypassing the signature check entirely — the classicalg:noneJWT vulnerability. The old code even had a dedicated success branch that rewarded exactly this forged payload.Fix
Both endpoints now call
Jwts.parser().parseClaimsJws(token)instead ofparse(token).parseClaimsJws()requires the token to be a genuine JWS and throwsUnsupportedJwtException(aJwtException) for any unsecured/alg:nonetoken, so a forged token's claims are never trusted. Added aJwtExceptioncatch innewToken()(previously unnecessary sinceparse()never threw for a successfully-forgedalg:nonetoken) returning 401, mirroring the existing behavior incheckout(). Removed the now-incorrect success-for-alg:nonebranch.Verification
parse()call accepts a forgedalg:nonetoken and extractsuser=Tom; the newparseClaimsJws()call rejects the identical forged token withUnsupportedJwtException; and a legitimately HS512-signed token for the same user is still accepted unchanged, so the real Tom/Jerry login → refresh → checkout flow keeps working.JWTRefreshEndpointTest:algNoneTokenShouldBeRejected(renamed fromsolutionWithAlgNone) now asserts the forged token is rejected (lessonCompleted=false,jwt-invalid-tokenfeedback) instead of asserting it solves the lesson. Existing tests for the legitimate flow, expired tokens, and malformed-signature tokens are unchanged in expectation.🤖 Generated with Claude Code