SQL Injection untuk Pemula: Saat Satu Tanda Kutip Bisa Membo...
SQL Injection untuk Pemula: Saat Satu Tanda Kutip Bisa Membobol Database
Kerentanan berumur lebih dari dua dekade ini masih rutin muncul di berita kebocoran data. Pahami kenapa query yang dibentuk dengan penyambungan string berbahaya, apa saja dampaknya, dan bagaimana prepared statement menutup celahnya untuk selamanya.
Kalau XSS adalah serangan yang menyerang sisi pengguna, maka SQL Injection adalah serangan yang menyerang jantung aplikasi Anda: databasenya.
Kerentanan ini sudah dikenal sejak akhir 1990-an. Umurnya lebih tua dari sebagian besar framework yang kita pakai hari ini. Namun anehnya, ia masih rutin muncul di berita kebocoran data — mulai dari sistem sekolah, e-commerce, sampai basis data pemerintah. Alasannya sederhana: kerentanan ini lahir dari kebiasaan menulis kode yang terasa alami, dan solusinya sering dianggap "nanti saja".
Mari kita bedah dari akarnya.
Akar Masalahnya: Data yang Dianggap Perintah
Jika Anda sudah membaca artikel XSS sebelumnya, pola ini akan terasa familiar. Penyebab dasarnya identik:
Aplikasi mencampur data dari pengguna dengan instruksi program, lalu menyerahkan campuran itu ke mesin yang tidak bisa membedakan keduanya.
Pada XSS, campuran itu diserahkan ke browser sebagai HTML/JavaScript. Pada SQL Injection, campuran itu diserahkan ke database sebagai perintah SQL.
Database tidak punya cara untuk tahu bagian mana yang "niat asli developer" dan bagian mana yang "titipan penyerang". Semuanya dibaca sebagai satu perintah utuh.
Contoh Klasik: Form Login
Perhatikan kode login berikut, yang sayangnya masih sering dijumpai:
// KODE RENTAN — jangan ditiru
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = $conn->query($sql);
Dalam kondisi normal, jika pengguna mengetik budi, query yang terbentuk adalah:
SELECT * FROM users WHERE username = 'budi' AND password = 'rahasia123'
Aman-aman saja. Masalahnya, kolom input itu menerima karakter apa pun, termasuk tanda kutip yang punya makna khusus dalam SQL.
Jika seseorang mengetik ' OR '1'='1 pada kolom username, query yang terbentuk berubah menjadi:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = ''
Kondisi '1'='1' selalu bernilai benar. Struktur logika query berubah total, dan database dengan patuh mengembalikan baris pengguna — biasanya akun pertama di tabel, yang sering kali justru akun administrator.
Penyerang berhasil masuk tanpa pernah tahu passwordnya. Ia tidak menebak kredensial; ia mengubah pertanyaan yang diajukan aplikasi ke database.
Perhatikan bahwa penyerang tidak memerlukan alat khusus untuk menemukan celah semacam ini. Cukup mengamati apakah aplikasi berperilaku aneh ketika input mengandung tanda kutip.
Bentuk-Bentuk SQL Injection
Secara garis besar, ada tiga kategori yang perlu Anda kenali sebagai developer:
1. In-band (langsung). Hasil manipulasi terlihat langsung di halaman — entah berupa data yang bocor ke layar, atau pesan error database yang terlalu jujur.
2. Blind (buta). Aplikasi tidak menampilkan hasil atau error apa pun, tetapi perilakunya berbeda: halaman berubah sedikit, atau responsnya melambat beberapa detik. Perbedaan halus itu cukup untuk menyimpulkan isi database sepotong demi sepotong. Blind SQLi sering luput dari perhatian justru karena aplikasi terlihat "baik-baik saja".
3. Second-order (tertunda). Input berbahaya disimpan dulu ke database dalam keadaan tidak berbahaya, lalu memicu masalah ketika data itu dipakai di query lain di kemudian hari. Ini yang paling sering lolos dari pengujian, karena celahnya tidak muncul di titik input.
Dampaknya Tidak Berhenti di Kebocoran Data
Banyak yang mengira dampak SQL Injection hanya "data pengguna bocor". Padahal cakupannya jauh lebih luas:
- Pencurian data — kredensial, data pribadi, informasi transaksi.
- Bypass autentikasi — masuk sebagai administrator tanpa password.
- Manipulasi data — mengubah harga produk, saldo, nilai, atau status pesanan.
- Penghapusan data — kehilangan tabel penting jika tidak ada backup yang layak.
- Eskalasi ke server — pada konfigurasi database yang terlalu longgar, celah ini bisa menjadi pijakan untuk membaca file sistem atau menjalankan perintah.
- Konsekuensi hukum — kebocoran data pribadi kini punya implikasi regulasi yang nyata, termasuk di Indonesia melalui UU PDP.
Satu parameter yang lupa diamankan sudah cukup untuk membuka semua pintu di atas.
Solusi Utamanya: Prepared Statement
Kabar baiknya, pencegahan SQL Injection termasuk yang paling jelas di dunia keamanan aplikasi. Kuncinya adalah memisahkan perintah dari data sejak awal, bukan mencoba membersihkan data setelahnya.
Inilah yang dilakukan prepared statement (parameterized query): struktur query dikirim lebih dulu ke database, lalu nilai dikirim terpisah sebagai parameter. Database sudah "mengunci" bentuk perintahnya, sehingga apa pun isi parameter akan diperlakukan murni sebagai data — bukan sebagai instruksi.
PHP (PDO):
$stmt = $pdo->prepare(
"SELECT * FROM users WHERE username = ? AND status = ?"
);
$stmt->execute([$username, 'active']);
$user = $stmt->fetch();
Node.js (node-postgres):
const result = await pool.query(
'SELECT * FROM users WHERE username = $1 AND status = $2',
[username, 'active']
);
Python:
cursor.execute(
"SELECT * FROM users WHERE username = %s AND status = %s",
(username, "active")
)
Sekarang, jika pengguna mengetik ' OR '1'='1, database akan mencari pengguna yang namanya benar-benar berupa teks ' OR '1'='1. Tentu saja tidak ada, dan query gagal dengan tenang — persis seperti yang kita inginkan.
Aturan praktis yang mudah diingat: jangan pernah menyambung string untuk membentuk query. Setiap nilai yang berasal dari luar aplikasi harus masuk sebagai parameter.
Lapisan Pertahanan Tambahan
Prepared statement adalah pertahanan utama, tetapi beberapa lapis berikut membuat aplikasi Anda jauh lebih tangguh:
Gunakan allowlist untuk bagian yang tidak bisa diparameterkan. Nama tabel, nama kolom, dan arah pengurutan tidak bisa dijadikan parameter. Untuk kasus ini, cocokkan dengan daftar nilai yang sah:
const kolomValid = ['nama', 'harga', 'tanggal'];
const arahValid = ['ASC', 'DESC'];
const kolom = kolomValid.includes(req.query.sort) ? req.query.sort : 'nama';
const arah = arahValid.includes(req.query.order) ? req.query.order : 'ASC';
Terapkan hak akses seminimal mungkin. Akun database yang dipakai aplikasi web tidak perlu izin DROP, CREATE USER, atau akses ke seluruh database. Jika suatu saat celah tetap lolos, dampaknya jauh lebih terbatas.
Jangan tampilkan error database ke pengguna. Pesan error mentah membocorkan struktur tabel dan versi database. Tampilkan pesan umum ke pengguna, dan simpan detailnya di log internal.
Hati-hati dengan raw query di ORM. ORM seperti Eloquent, Prisma, atau Sequelize memang aman secara default — tetapi hanya selama Anda memakai query builder-nya. Begitu Anda memakai whereRaw() atau sejenisnya dengan string sambungan, perlindungannya hilang.
Rajin memperbarui dependensi. Untuk pengguna WordPress, mayoritas kasus SQL Injection bukan berasal dari kode inti, melainkan dari plugin pihak ketiga yang jarang diperbarui.
Tiga Mitos yang Perlu Ditinggalkan
"Cukup escape tanda kutip secara manual." Pendekatan ini rapuh. Ada banyak variasi encoding dan konteks query yang membuat filter buatan sendiri bocor. Prepared statement menyelesaikan masalah di akarnya.
"Saya pakai ORM, jadi otomatis aman." Aman selama tidak menyentuh raw query. Banyak insiden justru bermula dari satu fungsi pencarian yang "terpaksa" ditulis manual.
"Aplikasi saya kecil, tidak ada yang tertarik." Sebagian besar serangan hari ini dilakukan oleh bot yang memindai internet secara acak. Bot tidak peduli seberapa terkenal situs Anda; ia hanya mencari parameter yang rentan.
Penutup
SQL Injection bertahan selama lebih dari dua dekade bukan karena sulit dicegah, melainkan karena mudah dilupakan. Cukup satu endpoint lama, satu fitur pencarian yang ditulis buru-buru, atau satu plugin yang tidak pernah diperbarui.
Kabar baiknya, pencegahannya tidak menuntut arsitektur rumit atau perangkat mahal. Cukup satu kebiasaan yang dijaga konsisten: perlakukan setiap input sebagai data, tidak pernah sebagai perintah.
Jika hari ini Anda hanya sempat melakukan satu hal, telusuri kode Anda dan cari query yang masih dibentuk dengan penyambungan string. Di sanalah biasanya masalah menunggu.
Post Terkait
CSRF untuk Pemula: Ketika Browser Anda Sendiri Jadi Senjata Penyerang
CSRF memanfaatkan satu kebiasaan browser: mengirim cookie sesi ke domain tujuan tanpa peduli halaman mana yang memicunya...
CORS untuk Pemula: Bukan Serangan, Tapi Salah Konfigurasinya Bisa Fatal
Berbeda dengan XSS yang jelas-jelas sebuah serangan, CORS sebenarnya adalah mekanisme keamanan browser. Masalahnya muncu...
XSS (Cross-Site Scripting): Cara Kerja Serangan dan Pencegahannya
Bedah tuntas cara kerja Cross-Site Scripting (XSS): tiga jenisnya, pola berpikir penyerang saat mencari celah, dan cara...