Skip to content

Fix Challenge-8-IDOR-Bank: bind bank balance/transfer to the session's own account - #344

Closed
beanbeah wants to merge 1 commit into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-8-idor-bank
Closed

beanbeah wants to merge 1 commit into
OWASP-CTF:dc34-ctffrom
beanbeah:ctf/r2-challenge-challenge-8-idor-bank

Conversation

@beanbeah

@beanbeah beanbeah commented Aug 9, 2026

Copy link
Copy Markdown

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 by DirectObjectBankLogin):

  • DirectObjectBankCurrentBalance.doPost() looked up the balance of whatever accountNumber was posted, with no ownership check.
  • DirectObjectBankTransfer.doPost() moved funds out of whatever senderAccountNumber was posted, with no check that it was the caller's own account.

The seed data ships a Mr. Banks account (account_number = 0, balance 10000000000) 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 with senderAccountNumber=0 and 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 existing error.shouldNotBeHere message) whenever the caller-supplied account number doesn't match it:

  • DirectObjectBankCurrentBalance: accountNumber must equal the session's bound account.
  • DirectObjectBankTransfer: senderAccountNumber must equal the session's bound account. receiverAccountNumber is intentionally left unrestricted — sending money to any other account number is the legitimate, intended feature of the challenge.

DirectObjectBankLogin, DirectObjectBankLogout, and DirectObjectBankRegistration were 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 as admin/password, registered a bank account, and confirmed:

  • Before the fix: POSTing accountNumber=0 to ...CurrentBalance returned Mr. Banks' real balance, and POSTing senderAccountNumber=0/receiverAccountNumber=<my account>/transferAmount=6000000 to ...Transfer successfully drained the account and unlocked the challenge's result key.
  • After the fix: both requests are refused with the standard "you shouldn't be here" message and no funds move.
  • Legitimate flow still works: logging into my own account, refreshing my own balance, and transferring funds from my own account to another registered account all succeed exactly as before (the front-end always submits the session-bound account number from the hidden currentAccountNumber field, so normal play is unaffected).

mvn spotless:apply run before commit.

…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>
@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: d4d5f66 · scoring run

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

@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