SQL injection terjadi ketika input pengguna menyusup ke query SQL tanpa penanganan yang tepat. Di PHP, ada beberapa lapisan pertahanan yang bisa dipakai, dan pilihan tekniknya memengaruhi keamanan sekaligus kerumitan kode. Berikut perbandingan tujuh teknik untuk menghindari SQL injection di PHP, dari yang paling mendasar sampai yang paling terstruktur.

1. Prepared Statement dengan PDO: Standar yang Paling Direkomendasikan

PDO memisahkan struktur query dari data yang dikirim. Query dikirim ke database lebih dulu, lalu nilai diikat sebagai parameter. Database tidak akan pernah menafsirkan nilai itu sebagai bagian dari perintah SQL.

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();

PDO mendukung banyak driver database (MySQL, PostgreSQL, SQLite) dengan API yang sama. Kekurangannya, PDO tidak mendukung query bertingkat seperti stored procedure dengan parameter OUT sebaik MySQLi. Untuk aplikasi yang mungkin berganti database, PDO lebih fleksibel. Jika kamu masih memilih antara PDO dan MySQLi, perbandingan PDO vs MySQLi vs Query Builder bisa membantu memutuskan.

2. Prepared Statement dengan MySQLi: Alternatif yang Lebih Spesifik

MySQLi menyediakan prepared statement native untuk MySQL. Ada dua gaya: prosedural dan berorientasi objek. Gaya objek lebih ringkas dan lebih mudah dibaca.

$stmt = $mysqli->prepare('SELECT * FROM users WHERE email = ?');
$stmt->bind_param('s', $email);
$stmt->execute();
$result = $stmt->get_result();

MySQLi unggul dalam fitur khusus MySQL, misalnya multiple statement dan parameter binding dengan tipe data eksplisit. Namun, jika kamu berpindah ke PostgreSQL, seluruh kode query harus ditulis ulang. Pilih MySQLi hanya jika proyek memang terkunci pada MySQL dan kamu memanfaatkan fitur spesifiknya.

3. Query Builder: Abstraksi yang Mengurangi Risiko Human Error

Query builder seperti Laravel Query Builder atau Doctrine DBAL membungkus pembuatan query dalam method chain. Nilai yang dimasukkan lewat binding otomatis di-escape sesuai driver.

$users = DB::table('users')->where('email', $email)->get();

Risiko utamanya adalah penggunaan raw query di dalam builder. Banyak query builder menyediakan method whereRaw atau selectRaw yang, jika diisi input pengguna tanpa binding, membuka celah yang sama seperti query manual. Query builder cocok untuk tim yang ingin konsistensi dan keterbacaan, asalkan disiplin dalam menghindari raw query tanpa parameter.

4. Escaping Manual dengan mysql_real_escape_string: Tidak Lagi Cukup

Fungsi mysql_real_escape_string (dan padanannya di MySQLi) menambahkan backslash pada karakter berbahaya. Teknik ini sempat menjadi standar sebelum prepared statement populer. Masalahnya, escaping bergantung pada charset koneksi. Jika charset tidak diset dengan benar, karakter multibyte tertentu bisa lolos dan menyebabkan bypass. Fungsi ini juga sudah dihapus di PHP 7. Untuk kode lama, segera migrasikan ke prepared statement. Jika kamu masih mengelola kode lama, periksa juga cara menampilkan pesan error validasi PHP di form agar tidak membocorkan struktur query.

5. Validasi dan Sanitasi Input: Lapisan Pendukung, Bukan Pengganti

Validasi memastikan input sesuai format yang diharapkan, misalnya email harus mengandung ‘@’ dan domain, atau ID harus angka. Sanitasi membersihkan karakter yang tidak diizinkan. Keduanya penting, tetapi tidak bisa menggantikan prepared statement karena validasi bisa saja lolos untuk karakter yang tetap berbahaya dalam konteks SQL tertentu.

  • Gunakan filter_var() untuk validasi tipe data seperti email, URL, dan integer.
  • Batasi panjang input sesuai kolom database.
  • Terapkan whitelist untuk nilai yang memang terbatas, seperti kolom sorting atau arah ORDER BY.

Validasi yang baik juga mengurangi beban debugging. Saat menggabungkan validasi dengan pengambilan nilai form, pastikan kamu memilih metode yang tepat seperti dibahas di artikel pengambilan nilai input form di PHP.

6. Stored Procedure dengan Parameter: Keamanan di Sisi Database

Stored procedure memindahkan logika query ke database. Jika procedure menggunakan parameter dan tidak menyusun query dinamis di dalamnya, SQL injection bisa dicegah di lapisan database. Namun, stored procedure menambah kompleksitas deployment karena harus disinkronkan dengan kode aplikasi. Debugging juga lebih sulit karena error terjadi di sisi database. Teknik ini cocok untuk sistem yang sudah lama berjalan dengan stored procedure, bukan untuk proyek baru yang ingin pengembangan cepat.

7. ORM: Keamanan Bawaan dengan Biaya Performa

ORM seperti Eloquent (Laravel) atau Doctrine memetakan tabel ke objek dan menghasilkan query secara otomatis. Secara default, ORM menggunakan prepared statement, sehingga input pengguna tidak pernah masuk sebagai bagian dari string query.

$user = User::where('email', $email)->first();

ORM mengurangi kode boilerplate dan membuat query lebih mudah dibaca. Kekurangannya, ORM bisa menghasilkan query yang tidak efisien untuk kasus kompleks, dan penggunaan raw expression di dalam ORM mengembalikan risiko injection jika tidak hati-hati. ORM cocok untuk aplikasi dengan banyak relasi dan tim yang sudah familiar dengan pola MVC. Jika kamu baru memulai, pahami dulu dasar query manual sebelum beralih ke ORM.

Perbandingan Singkat

Teknik Kemudahan Portabilitas Database Cocok untuk
PDO prepared statement Sedang Tinggi Proyek baru, multi-database
MySQLi prepared statement Sedang Rendah Proyek khusus MySQL
Query builder Tinggi Tinggi Tim dengan konvensi jelas
Escaping manual Rendah Rendah Kode lama (segera migrasi)
Validasi input Tinggi Tinggi Selalu, sebagai pelengkap
Stored procedure Rendah Rendah Sistem legacy
ORM Tinggi Tinggi Aplikasi dengan relasi kompleks

Kesimpulan

Prepared statement, baik lewat PDO maupun MySQLi, tetap menjadi pertahanan utama terhadap SQL injection. Query builder dan ORM menambah lapisan kenyamanan asalkan kamu tidak menyisipkan input mentah ke dalam raw query. Validasi input dan sanitasi berperan sebagai pelengkap, bukan pengganti. Untuk proyek baru, mulai dengan PDO atau ORM yang sudah mendukung binding parameter secara default. Jika kamu perlu menyusun elemen DOM secara dinamis di sisi klien, teknik seperti menambahkan elemen baru ke DOM dengan jQuery juga perlu memperhatikan sanitasi agar tidak membuka celah XSS.

Pertanyaan yang Sering Diajukan

Apakah prepared statement cukup untuk mencegah semua SQL injection?

Prepared statement mencegah injeksi pada nilai parameter. Namun, jika kamu menyusun nama tabel, nama kolom, atau klausa ORDER BY dari input pengguna tanpa whitelist, celah tetap terbuka. Gunakan whitelist untuk bagian query yang tidak bisa diparameterisasi.

Apakah validasi input bisa menggantikan prepared statement?

Tidak. Validasi memastikan format input sesuai, tetapi karakter yang lolos validasi belum tentu aman dalam konteks SQL. Prepared statement memisahkan data dari perintah, sehingga tetap diperlukan meskipun validasi sudah ketat.

Kapan sebaiknya memakai ORM daripada query manual?

ORM cocok ketika aplikasi memiliki banyak relasi tabel dan tim ingin mengurangi kode boilerplate. Untuk query analitik yang kompleks atau performa kritis, query manual dengan prepared statement sering lebih tepat karena kamu bisa mengendalikan rencana eksekusi database.