SQL Injection Prevention — Security

Mencegah SQL Injection Ada beberapa teknik untuk mencegah SQL Injection. Teknik utama dan paling efektif adalah parameterized queries (prepared statements)…

Bayangin formulir kertas di bank: kolomnya sudah dicetak ("Nama:", "Jumlah:"), dan kamu cuma boleh ngisi bagian kosongnya. Apa pun yang kamu tulis di kolom kosong tetap dianggap isian, bukan perintah baru buat petugasnya. Parameterized query (sering disebut prepared statement) bekerja persis begitu: struktur query sudah dicetak dulu, input user cuma mengisi kolom kosong, jadi gak bisa ikut jadi perintah SQL.

🧪 Tebakan cepat: query aman SELECT * FROM users WHERE username = ? dikirim input ' OR '1'='1. Query-nya bocor dan mengembalikan semua user, atau gak ketemu siapa-siapa? Tebak dulu, jawabannya ada di bagian "Parameterized query: pisah query dari data".

Di lesson sebelumnya kamu lihat gimana SQL injection bekerja. Sekarang fokusnya satu: cara menulis query yang kebal. Satu teknik mengalahkan hampir semua kasus, dan teknik itu yang kita latih.

Masalahnya: query dan data nyampur

Query yang rentan menyusun SQL dengan menyambung (concatenation) input user langsung ke dalam teks query:

// RENTAN: input disambung jadi teks query
const q = "SELECT * FROM users WHERE username = '" + input + "'";
db.exec(q);

Kalau input berisi nama biasa seperti budi, query jadi ... WHERE username = 'budi', aman. Tapi kalau input berisi ' OR '1'='1, teks query-nya berubah jadi:

SELECT * FROM users WHERE username = '' OR '1'='1'

Intinya: input berubah jadi kode karena dia dicampur ke teks query. Itu akar masalahnya.

Parameterized query: pisah query dari data

Perbaikannya: tulis query dengan placeholder (tanda ? atau :nama) di tempat datanya, lalu kirim nilainya terpisah. Driver database menggabungkan keduanya, dan nilai yang kamu kirim selalu diperlakukan sebagai satu data utuh, gak pernah sebagai SQL.

// AMAN: ? adalah placeholder, input dikirim terpisah
db.exec("SELECT * FROM users WHERE username = ?", [input]);

Sekarang kirim lagi input ' OR '1'='1. Query-nya tetap SELECT * FROM users WHERE username = ?, dan driver mencari user yang username-nya persis teks ' OR '1'='1 (lengkap dengan kutip dan spasinya). Gak ada user bernama begitu, jadi hasilnya 0 baris. Itu jawaban tebakannya: query gak bocor, malah gak ketemu siapa-siapa.

Di PHP pakai PDO, placeholder-nya diberi nama dan query disiapkan dulu dengan prepare():

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([":username" => $username]);

ORM: aman secara default

Framework ORM seperti Eloquent (Laravel) atau Prisma otomatis memakai parameter binding di balik layar. Kamu nulis query dengan method, bukan dengan teks:

// Eloquent: where() otomatis mem-bind nilainya terpisah
$user = User::where('username', $username)->first();

where('username', $username) aman dari SQL injection karena $username di-bind sebagai data, sama seperti ?. Kamu gak perlu memikirkan kutip atau escape sama sekali.

Validasi tipe sebagai lapisan tambahan

Kalau sebuah input memang harus angka, periksa dulu sebelum dipakai:

$id = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT);
if ($id === false) {
  http_response_code(400);
  exit("ID tidak valid");
}

Validasi bukan pengganti parameterized query, tapi lapisan tambahan: input yang jelas salah bentuk ditolak lebih awal.

⚠️ Jebakan yang sering ketemu

TL;DR: SQL injection dicegah dengan memisahkan query dari data. Jangan sambung input ke teks query; pakai placeholder ? atau :nama dan kirim nilainya terpisah lewat prepare() lalu execute(). Input ' OR '1'='1 jadi dicari sebagai teks literal, gak ketemu, mengembalikan 0 baris, bukan membocorkan semua baris. ORM seperti Eloquent (where('username', $username)) aman secara default karena mem-bind nilai otomatis. Escape dan blacklist gak cukup; validasi tipe cuma lapisan tambahan.

Yang akan kamu pelajari