Back to the index

LAB PASS

Broken Object Level Authorization - GraphQL - Transaction Summary

Broken Object Level Authorization — GraphQL — Transaction Summary

CWE-639: Authorization Bypass Through User-Controlled Key · OWASP A01:2021 - Broken Access Control

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.

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
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
    }
  }
}
Screenshot
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"
      ]
    }
  ]
}
Screenshot
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"
      ]
    }
  ]
}
Screenshot

← Previous case: Broken Object Level Authorization - Accounts - Balance Lookup Next case: Broken Object Level Authorization - Merchant Payments - Payment By ID →