Belajar Cyber Security #8: SQL Injection — dari String Concatenation ke Prepared Statement

Di episode #7 kita sudah memetakan OWASP Top 10:2025. Salah satu kategori yang menurut saya paling “menarik” sekaligus paling memalukan untuk programmer adalah A05: Injection — karena ini bug paling tua di buku, tapi tetap juara CWE/CVE di daftar OWASP (38 CWE terkait injection). Dan bentuknya yang paling legendaris? SQL Injection. Hari ini saya coba membedahnya dari sisi programmer yang selama ini nyaman menempelkan variabel user ke query database.

Kenapa SQL Injection masih ada di 2026

Saya sempat berpikir SQLi itu bug masa lalu — sudah ada ORM, sudah ada prepared statement, kenapa masih dibahas? Ternyata alasannya sederhana: database hanya bisa menerima perintah dalam bentuk teks, dan selama ada programmer yang merangkai teks query dengan cara “paling cepat”, celahnya selalu ada. Bahkan di DEF CON 32 (2024) masih ada presentasi “SQL Injection Isn’t Dead” — tekniknya bukan lagi ' OR '1'='1 polos, tapi sudah bergeser ke second-order injection dan penyelundupan data lewat jalur yang dianggap “aman”.

Mekanismenya: query yang berpikir input user adalah kode

Inti masalahnya satu: database tidak bisa membedakan mana bagian query yang ditulis developer, mana bagian yang diketik user. Kalau keduanya dirangkai jadi satu string, user diam-diam ikut “menulis” query-nya.

// Berbahaya: input user jadi bagian dari teks query
$username = $_POST['username'];
$password = $_POST['password'];

$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);

Kalau user mengetik ' OR '1'='1 sebagai password, kondisi di WHERE selalu bernilai benar, dan seluruh baris users ikut terambil. Kalau ini dipakai di halaman login, attacker masuk tanpa perlu tahu satu pun password. Kalau diperparah dengan UNION SELECT, data dari tabel lain (kartu kredit, token, data pribadi) bisa ikut terangkat ke halaman yang sama.

Empat wujud SQLi yang saya catat

Second-order yang paling bikin saya merinding, karena input-nya sudah lolos validasi di form, tersimpan rapi, dan baru “meledak” beberapa hari kemudian di laporan admin.

Kasus nyata: TalkTalk 2015

Bukan cuma lab CTF. Oktober 2015, operator telekomunikasi Inggris TalkTalk kena serangan SQL Injection lewat tiga halaman web legacy warisan akuisisi Tiscali — halaman yang bahkan tidak diketahui tim keamanannya masih hidup. Investigasi Information Commissioner’s Office (ICO) menemukan 156.959 data pelanggan bocor (nama, alamat, tanggal lahir, nomor telepon), dan pada 15.656 kasus ikut membawa nomor rekening plus sort code. Database yang dipakai pun sudah usang, punya bug yang sudah tersedia patch-nya. ICO menjatuhkan denda rekor £400.000 pada Oktober 2016. Pelajaran yang saya ambil: SQLi tidak harus canggih — cukup satu halaman lama yang dilupakan.

Obatnya: parameterized query, bukan “bersihkan input”

OWASP SQL Injection Prevention Cheat Sheet menempatkan parameterized query (prepared statement) sebagai pertahanan utama. Ide-nya berbeda total dari sanitasi: struktur query dikunci dulu, data user dikirim terpisah sebagai parameter — database tahu mana SQL, mana data, jadi data tidak akan pernah dieksekusi sebagai kode.

// Aman: query dan data terpisah
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :u AND password = :h');
$stmt->execute(['u' => $username, 'h' => $passwordHash]);
$user = $stmt->fetch();

Di Laravel yang saya pakai sehari-hari, ini praktis sudah menjadi default selama kita tidak melawan framework: DB::select('SELECT * FROM users WHERE id = ?', [$id]) atau method Eloquent dengan binding otomatis. Lubangnya biasanya muncul saat kita butuh query dinamis lalu menempelkan string: DB::select("... WHERE status = '" . $status . "'"). Itu baris yang harus saya jaga untuk tidak pernah ditulis.

Tiga lapis pertahanan tambahan yang saya catat sebagai pengingat diri sendiri:

  • DB user ber-privilege minimum — aplikasi web seharusnya tidak butuh hak DROP atau akses tabel lain yang tidak terkait; TalkTalk sempat memperparah dampak karena skema database berlebih.
  • Jangan tampilkan error database ke user — pesan error mentah membantu attacker menyusun payload (log error untuk developer, halaman generik untuk publik).
  • ORM bukan jaminan kalau query mentah ditulis sendiri — raw query dengan konkatenasi string tetap injection, apa pun framework-nya.

WAF atau filter input bisa membantu sebagai lapis tambahan, tapi saya memahaminya sekarang sebagai “pagar”, bukan “dinding” — solusi utamanya tetap jangan pernah rangkai string jadi query.

Yang saya terapkan minggu ini

Saya audit project lama yang isinya query string konkatenasi, ganti satu per satu ke prepared statement, matikan display error database di environment production, dan pastikan database user aplikasi hanya punya hak baca/tulis di tabel yang benar-benar dipakai. Ini pekerjaan yang tidak glamor, tapi membayangkan nama alamat 156.959 orang bocor karena tiga halaman lupa dihapus cukup menjadi motivasi. Di episode #6 kita bicara pertahanan di level jaringan; SQL injection mengingatkan saya bahwa attack surface terbesar justru sering ada di baris kode query kita sendiri. Episode berikutnya kita lanjut ke XSS — sepupu injection yang menyasar browser, bukan database.

Sumber

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Situs ini menggunakan Akismet untuk mengurangi spam. Pelajari bagaimana data komentar Anda diproses

© 2026 Catatan Rudy

JenisCara kerjaTanda
In-band (union/error)Hasil ekstraksi tampil langsung di halaman, sering lewat UNION SELECT atau pesan error databaseHalaman berubah isi atau menampilkan error SQL mentah
Boolean-based blindTidak ada output langsung; attacker menebak data satu bit per request lewat perbedaan respons benar/salahBanyak request dengan pola mirip tapi isi parameter berganti
Time-based blindSama seperti blind, tapi memakai jeda waktu (SLEEP/BENCHMARK) sebagai sinyalRespons melambat drastis tanpa sebab
Second-orderInput “bersih” disimpan ke database, lalu dipakai ulang di query lain tanpa parameterisasiBUG ada di query bagian dalam, bukan di form awal