Test case 23 / 26PASS

Broken Object Level Authorization - GraphQL - Transaction Summary

Lab environment — not a production system

Screenshots and raw traffic are not translated; text within them may be in another language.

vuln-bank/Evidence/[PASS] Broken Object Level Authorization - GraphQL - Transaction Summary

01 — Notes

Test notes

Vulnerability

[PASS] Broken Object Level Authorization - GraphQL - Transaction Summary

Description

The test attempted to read another user's transaction summary through the accountNumber argument of the GraphQL transactionSummary resolver, including the victim's account and the administrator's account.

Impact

No impact. Control holds: the resolver compares the requested accountNumber with the account owned by the token holder and rejects cross-object requests with an explicit error. Ordinary users can only read the transaction summary of their own account. Note: the same code path is still flawed for callers with the administrator role, because the argument value is passed into the SQL query without parameterization; this is reported separately in the SQL Injection - GraphQL finding.

CWE

CWE-639: Authorization Bypass Through User-Controlled Key

OWASP

A01:2021 - Broken Access Control

Scenario

Normal: an ordinary user requests their own account transaction summary -> succeeds. Attempt: accountNumber is changed to the victim's account -> rejected. Attempt: accountNumber is changed to the administrator's account -> rejected.

Endpoint

POST http://127.0.0.1:5001/graphql

Affected feature

GraphQL transactionSummary (object authorization)

Remediation

- Keep the account ownership check in the resolver. - Add parameterized queries so the same argument does not become an injection point for higher-privileged callers.

Proof of concept

1) (Screenshot 1) NORMAL: an ordinary user requests their own account transaction summary -> succeeds. 2) (Screenshot 2) ATTEMPT: accountNumber is changed to the victim's account -> rejected with the error You can only query your own transaction summary, control holds. 3) (Screenshot 3) ATTEMPT: accountNumber is changed to the administrator's account -> rejected, control holds.
View source note (English)
Vulnerability:
[PASS] Broken Object Level Authorization - GraphQL - Transaction Summary

Description:
The test attempted to read another user's transaction summary through the accountNumber argument of the GraphQL transactionSummary resolver, including the victim's account and the administrator's account.

Impact:
No impact. Control holds: the resolver compares the requested accountNumber with the account owned by the token holder and rejects cross-object requests with an explicit error. Ordinary users can only read the transaction summary of their own account.

Note: the same code path is still flawed for callers with the administrator role, because the argument value is passed into the SQL query without parameterization; this is reported separately in the SQL Injection - GraphQL finding.

CWE:
CWE-639: Authorization Bypass Through User-Controlled Key

OWASP:
A01:2021 - Broken Access Control

Scenario:
Normal: an ordinary user requests their own account transaction summary -> succeeds. Attempt: accountNumber is changed to the victim's account -> rejected. Attempt: accountNumber is changed to the administrator's account -> rejected.

Endpoint:
POST http://127.0.0.1:5001/graphql

Affected Feature:
GraphQL transactionSummary (object authorization)

Remediation:
- Keep the account ownership check in the resolver.
- Add parameterized queries so the same argument does not become an injection point for higher-privileged callers.

POC:
1) (Screenshot 1) NORMAL: an ordinary user requests their own account transaction summary -> succeeds.
2) (Screenshot 2) ATTEMPT: accountNumber is changed to the victim's account -> rejected with the error You can only query your own transaction summary, control holds.
3) (Screenshot 3) ATTEMPT: accountNumber is changed to the administrator's account -> rejected, control holds.

02 — Screenshots

Screenshots (3)

01 / 1
02 / 2
03 / 3

03 — Requests and responses

HTTP captures

Some credential values are masked in this public HTML view. This does not mean every sensitive value or linked file has been redacted.

CAPTURE 01 / 1
HTTP request
POST /graphql 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]
Content-Type: application/json
Content-Length: 148

{"query": "query($a:String){transactionSummary(accountNumber:$a,limit:3){scope accountNumber totalTransactions}}", "variables": {"a": "0932191785"}}
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 149
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:13:13 GMT

{
  "data": {
    "transactionSummary": {
      "accountNumber": "0932191785",
      "scope": "account",
      "totalTransactions": 11
    }
  }
}
CAPTURE 02 / 2
HTTP request
POST /graphql 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]
Content-Type: application/json
Content-Length: 148

{"query": "query($a:String){transactionSummary(accountNumber:$a,limit:3){scope accountNumber totalTransactions}}", "variables": {"a": "9159912486"}}
HTTP response
HTTP/1.1 400 BAD REQUEST
Content-Type: application/json
Content-Length: 361
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:13:14 GMT

{
  "data": {
    "transactionSummary": null
  },
  "errors": [
    {
      "message": "You can only query your own transaction summary.\n\nGraphQL request:1:18\n1 | query($a:String){transactionSummary(accountNumber:$a,limit:3){scope accountNumber totalTransactions}}\n  |                  ^",
      "path": [
        "transactionSummary"
      ]
    }
  ]
}
CAPTURE 03 / 3
HTTP request
POST /graphql 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]
Content-Type: application/json
Content-Length: 146

{"query": "query($a:String){transactionSummary(accountNumber:$a,limit:3){scope accountNumber totalTransactions}}", "variables": {"a": "ADMIN001"}}
HTTP response
HTTP/1.1 400 BAD REQUEST
Content-Type: application/json
Content-Length: 361
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:13:15 GMT

{
  "data": {
    "transactionSummary": null
  },
  "errors": [
    {
      "message": "You can only query your own transaction summary.\n\nGraphQL request:1:18\n1 | query($a:String){transactionSummary(accountNumber:$a,limit:3){scope accountNumber totalTransactions}}\n  |                  ^",
      "path": [
        "transactionSummary"
      ]
    }
  ]
}