Conversation
…s own account
DirectObjectBankCurrentBalance and DirectObjectBankTransfer trusted the
client-supplied accountNumber / senderAccountNumber outright, never
checking it against the bank account the session actually authenticated
into (ses.getAttribute("directObjectBankAccount"), set at login). Any
signed-in player could read another account's balance, or drain funds
out of it (e.g. the seeded high-balance 'Mr. Banks' account) by simply
naming it as the sender in a transfer request.
Both servlets now resolve the session's own bound account and refuse
the request with the existing error.shouldNotBeHere message whenever
the supplied account/sender number does not match it. receiverAccountNumber
in Transfer is left unrestricted since sending funds to any other
account is the legitimate, intended feature.
Verified locally (WSL, mvn -Pdocker + docker compose, MariaDB/Mongo/
Tomcat): before the fix, POSTing senderAccountNumber=<Mr. Banks' id>
with my own account as receiver successfully drained the account and
unlocked the challenge result key; after the fix the same request is
refused and no funds move, while refreshing/transferring from my own
account still works exactly as before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
🏆 Security Shepherd — CTF Patch Score1 / 40 challenges patched
Commit: 🎉 Your result is on the leaderboard — see where you rank! 🏆 |
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 Insecure Direct Object Reference Bank challenge exposes two servlets that read and mutate a bank account purely from an attacker-controlled request parameter, never checking it against the bank account the current session actually authenticated as (
ses.getAttribute("directObjectBankAccount"), set byDirectObjectBankLogin):DirectObjectBankCurrentBalance.doPost()looked up the balance of whateveraccountNumberwas posted, with no ownership check.DirectObjectBankTransfer.doPost()moved funds out of whateversenderAccountNumberwas posted, with no check that it was the caller's own account.The seed data ships a
Mr. Banksaccount (account_number = 0, balance10000000000) whose password is intentionally un-hashed so it can never be logged into directly — but because the sender/account-number checks were missing, any signed-in player could still read its balance via the IDOR, or drain it by POSTing a transfer withsenderAccountNumber=0and their own account as the receiver, instantly clearing the ">5,000,000 balance" win condition without ever compromising the account's real credentials.Fix
Both servlets now resolve the session's own bank account (
ses.getAttribute("directObjectBankAccount")) and reject the request (logging the attempt and returning the existingerror.shouldNotBeHeremessage) whenever the caller-supplied account number doesn't match it:DirectObjectBankCurrentBalance:accountNumbermust equal the session's bound account.DirectObjectBankTransfer:senderAccountNumbermust equal the session's bound account.receiverAccountNumberis intentionally left unrestricted — sending money to any other account number is the legitimate, intended feature of the challenge.DirectObjectBankLogin,DirectObjectBankLogout, andDirectObjectBankRegistrationwere reviewed and already correctly scope all access through the session; no changes were needed there.Testing
Built and ran the full stack locally (WSL,
mvn -Pdocker clean install -DskipTests+docker compose build/up, MariaDB + Mongo + Tomcat). Logged in asadmin/password, registered a bank account, and confirmed:accountNumber=0to...CurrentBalancereturned Mr. Banks' real balance, and POSTingsenderAccountNumber=0/receiverAccountNumber=<my account>/transferAmount=6000000to...Transfersuccessfully drained the account and unlocked the challenge's result key.currentAccountNumberfield, so normal play is unaffected).mvn spotless:applyrun before commit.