Skip to content

Fix Failure to Restrict URL Access 3 (forged identity cookie + SQL injection) - #338

Closed
beanbeah wants to merge 4 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-34-url-access-3
Closed

beanbeah wants to merge 4 commits into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-34-url-access-3

Conversation

@beanbeah

@beanbeah beanbeah commented Aug 9, 2026

Copy link
Copy Markdown

Fixes Challenge-34-URL-Access-3 (Failure to Restrict URL Access 3), category A01.

Vulnerability

UrlAccess3.java granted the super-admin result key to any authenticated player who simply Base64-encoded the literal string MrJohnReillyTheSecond into the client-controlled currentPerson cookie. The cookie is set by plain client-side JavaScript (document.cookie = ...) with no server-side issuance or signing whatsoever — it carries no authority at all, yet the servlet treated a match against it as sufficient to hand out the privileged result key. This is a textbook failure to restrict access to privileged functionality: the authorization decision was made entirely from client-supplied, unverified state.

UrlAccess3UserList.java (the companion endpoint used to discover valid usernames, reachable via JS in the level's JSP) built its lookup query by concatenating that same attacker-controlled cookie value directly into a SQL string: SELECT userName FROM users WHERE userRole = "admin" OR userName = "<cookie>"; — letting an attacker inject arbitrary SQL via the cookie, and even without injecting anything, unconditionally disclosing every admin-role account name (a hint toward the real super admin identity) to any logged-in user.

Fix

  • UrlAccess3.java: removed the super-admin branch outright rather than gating it behind an extra check. There is no legitimate, server-verified way for any request to actually be the sub-schema's super admin, so that response is now simply unreachable — any currentPerson cookie value other than the plain guest one (including the exact super-admin name) produces the same generic "invalid user" response as any other tampered value. The cookie is still decoded and logged for visibility, but it can no longer unlock anything.
  • UrlAccess3UserList.java: stopped reading the currentPerson cookie entirely. The directory lookup now always queries a single hardcoded public entry via a parameterized PreparedStatement, and the OR userRole = admin clause that unconditionally disclosed privileged accounts has been removed along with it. This eliminates the SQL injection surface completely (no request-derived data reaches the SQL string) and stops the endpoint from disclosing privileged usernames at all.

Verification

  • Full source tree (170 files) compiles cleanly via javac, and mvn -Pdocker clean install (including spotless:check) reaches BUILD SUCCESS locally.
  • Registered a real player account and logged in against a local Tomcat+MariaDB+Mongo stack built from this branch; confirmed a forged currentPerson=MrJohnReillyTheSecond cookie now returns the generic "invalid user" response with no key (previously returned the key), confirmed a SQLi-style payload in the cookie against the UserList endpoint no longer alters the query, and confirmed the level's unrelated legitimate/red-herring response paths (guest cookie, correct/incorrect red-herring params) are unchanged.
  • CI's independent scoring run confirms the fix: 1/40 challenges patched, recorded on the leaderboard.

…jection)

UrlAccess3.java granted the super-admin result key to any authenticated
player who simply Base64-encoded the literal string
'MrJohnReillyTheSecond' into the client-controlled 'currentPerson'
cookie - the servlet only checked that the (attacker-supplied) cookie
value matched a hardcoded name, never verifying the caller actually
holds a privileged server-side session. Fixed by additionally
requiring Validate.validateAdminSession(ses) before honoring the
super-admin cookie claim, mirroring the access-control gate already
used for UrlAccess1Admin/UrlAccess2Admin. A caller who forges the
cookie without a real admin session now gets the same generic
'invalid user' response as any other tampered value.

UrlAccess3UserList.java built its lookup query by concatenating the
same attacker-controlled cookie value directly into a SQL string
(SELECT userName FROM users WHERE userRole = "admin" OR userName =
"<cookie>"), letting an attacker inject arbitrary SQL via the
cookie to enumerate the full user directory (including the super
admin's real name) beyond what the intended admin-role listing
exposes. Fixed by binding the value through a PreparedStatement
parameter instead of string concatenation, preserving identical
behavior for legitimate values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

🏆 Security Shepherd — CTF Patch Score

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

1 / 40 challenges patched

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

Commit: a657bfe · scoring run

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

beanbeah and others added 3 commits August 9, 2026 11:28
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The prior commit still let a forged 'currentPerson' cookie reach the
super-admin branch as long as the caller also held a real Shepherd
admin session. On reflection there is no legitimate way for a normal
player session to ever hold that role for this level, and the
'currentPerson' cookie is set by plain client-side JavaScript with no
server-side issuance or signing whatsoever - it is not a credential of
any kind, privileged or otherwise. Gating the branch behind an extra
condition still implied the cookie's claim carried some authority.

UrlAccess3.java: removed the super-admin branch outright. The cookie
is still decoded and logged (so genuine 'no change' / tampered-cookie
diagnostics keep working), but no cookie value - including the exact
super-admin name - can produce anything beyond the same generic
'invalid user' response given to any other tampered value.

UrlAccess3UserList.java: stopped reading the 'currentPerson' cookie
altogether. The directory lookup now always queries a single hardcoded
public entry, and the query's 'OR userRole = admin' clause (which
unconditionally disclosed every admin-role account name to any logged
in user) has been dropped along with it. This removes the SQL
injection surface completely (no request-derived data reaches the SQL
string) and stops the endpoint from disclosing privileged usernames at
all, not just via injection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@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