SQL Injection — jenis serangan pada database aplikasi di mana penyerang menyuntikkan kode SQL berbahaya ke dalam parameter permintaan, mendapatkan akses tidak sah ke data atau kemampuan untuk memodifikasinya. Menurut data OWASP (2025), SQL Injection tetap menjadi salah satu kerentanan kritis yang dapat menyebabkan kompromi total pada database. Injeksi kode SQL memungkinkan penyerang membaca, mengubah, dan menghapus catatan, dan dalam beberapa kasus — mendapatkan akses ke sistem operasi server.
Poin utama
SQL Injection adalah kerentanan yang muncul ketika aplikasi membentuk permintaan SQL melalui penggabungan string dengan data pengguna. Penyerang mengirimkan string yang dibuat khusus ke parameter permintaan yang mengubah struktur perintah SQL. Alih-alih menjadi data, string yang dimasukkan menjadi bagian dari kode SQL, memungkinkan penyerang untuk menjalankan permintaan sewenang-wenang ke database. Menurut Verizon Data Breach Report (2025), SQL Injection hadir dalam 8% dari semua kebocoran data yang diteliti.
Meskipun kerentanan ini sudah dikenal (pertama disebutkan — akhir 1990-an), SQL Injection masih ditemukan di aplikasi modern. Penyebabnya — faktor manusia: pengembang membiarkan penggabungan string dalam kode, kode legacy tidak direfaktor, dan framework ORM digunakan secara tidak benar (misalnya, permintaan mentah dengan substitusi nilai melalui string). Menurut data Veracode (2026), sekitar 14% dari semua aplikasi yang dipindai mengandung setidaknya satu kerentanan SQLi.
SQL Injection yang berhasil memberikan penyerang berbagai kemampuan: membaca tabel database apa pun, termasuk hash kata sandi dan data pribadi pengguna; memodifikasi dan menghapus catatan; menjalankan operasi administratif (DROP TABLE, TRUNCATE); dalam beberapa konfigurasi — eksekusi perintah jarak jauh melalui xp_cmdshell (MSSQL) atau INTO OUTFILE (MySQL). Konsekuensinya — mulai dari kebocoran data pengguna hingga hilangnya kendali penuh atas sistem.
Injeksi SQL diklasifikasikan berdasarkan cara ekstraksi data dari database. Pemilihan metode tergantung pada bagaimana aplikasi memproses hasil permintaan dan pesan kesalahan. Klasifikasi OWASP membedakan tiga jenis utama: In-band (data diekstrak melalui saluran yang sama), Inferential/Blind (kesimpulan logis) dan Out-of-band (data dikirim melalui saluran lain).
| Jenis | Cara ekstraksi data | Kompleksitas | Frekuensi |
|---|---|---|---|
| In-band (klasik) | Langsung melalui hasil permintaan | Rendah | Tinggi |
| Blind SQLi | Kesimpulan logis berdasarkan respons server | Tinggi | Sedang |
| Out-of-band | Melalui saluran eksternal (DNS, HTTP) | Sedang | Rendah |
Jenis yang paling umum. Penyerang menyuntikkan kode SQL ke parameter permintaan, dan hasil injeksi terlihat langsung dalam respons server. Dua subtipe: Error-based (melalui pesan kesalahan database) dan UNION-based (melalui operator UNION SELECT). Error-based menggunakan informasi dari pesan kesalahan, misalnya kesalahan sintaks MySQL yang dapat mengungkapkan nama tabel atau struktur permintaan. UNION-based memungkinkan menggabungkan hasil permintaan yang sah dengan data dari tabel database lain.
Digunakan ketika aplikasi tidak menampilkan hasil permintaan atau pesan kesalahan. Penyerang mengajukan pertanyaan „ya/tidak” dengan mengirimkan permintaan dengan kondisi logis dan menganalisis perbedaan dalam respons server (misalnya, waktu respons atau konten halaman). Time-based Blind SQLi menggunakan fungsi penundaan (SLEEP, WAITFOR DELAY) untuk mengonfirmasi kondisi — jika halaman memuat lebih lama, kondisi tersebut benar. Metode ini sangat lambat — mengekstrak satu catatan bisa memakan waktu berjam-jam.
# Contoh Blind SQL Injection (time-based)
# Jika SQLi rentan, SLEEP(2) dijalankan dengan syarat
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# Jika respons datang setelah >2 detik — huruf pertama kata sandi adalah 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
Data dikirim bukan melalui respons HTTP, tetapi melalui saluran alternatif: permintaan DNS, permintaan HTTP ke server eksternal, SMTP. Digunakan ketika aplikasi tidak mengembalikan hasil permintaan dan tidak menampilkan kesalahan. MySQL mendukung fungsi LOAD_FILE(), yang dapat memulai permintaan DNS, dan MSSQL — xp_dirtree untuk mengirim data ke server SMB jarak jauh. Out-of-band SQL Injection efektif, tetapi memerlukan pengaturan tambahan di server penyerang dan keberadaan fungsi database tertentu.
Mekanisme SQL Injection didasarkan pada kenyataan bahwa SQL menggunakan tanda kutip untuk literal string. Jika aplikasi mensubstitusi input pengguna langsung ke permintaan SQL tanpa escaping, penyerang dapat „menutup” string dan menambahkan kode SQL sembarang. Misalnya, dalam permintaan SELECT * FROM users WHERE name = '$input' substitusi ' OR '1'='1 mengubah permintaan menjadi SELECT * FROM users WHERE name = '' OR '1'='1', yang mengembalikan semua pengguna.
Pertimbangkan formulir login dengan permintaan SELECT * FROM users WHERE username = '$user' AND password = '$pass'. Jika penyerang memasukkan di bidang username admin' -- dan membiarkan kata sandi kosong, permintaan yang dihasilkan menjadi SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. Karakter -- mengomentari sisa permintaan, dan kondisi pemeriksaan kata sandi dinonaktifkan. Server mengembalikan catatan pengguna admin, dan penyerang masuk ke sistem tanpa mengetahui kata sandi.
# Contoh SQL Injection — melewati autentikasi
# KODE RENTAN: penggabungan string langsung
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --" menonaktifkan pemeriksaan kata sandi
# VERSI AMAN: permintaan terparameterisasi
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
Operator UNION SELECT memungkinkan menggabungkan hasil dua permintaan SELECT. Jika penyerang menemukan parameter yang rentan, ia menambahkan UNION SELECT dengan permintaan dari tabel lain. Contoh: ' UNION SELECT username, password FROM admins --. Syarat keberhasilan — jumlah kolom di kedua permintaan harus cocok. Jumlah kolom ditentukan melalui ORDER BY (substitusi ' ORDER BY 1--, kemudian 2, 3... hingga kesalahan). Mengetahui jumlah kolom, penyerang mensubstitusi UNION SELECT dengan jumlah bidang yang sama.
Aplikasi mobile menghadapi SQL Injection baik di sisi server (API) maupun di sisi klien — di database lokal (SQLite, Realm). Meskipun risiko SQLi server di API mobile mirip dengan aplikasi web, database lokal menciptakan vektor tambahan. Jika aplikasi menyimpan data di SQLite dan menjalankan permintaan dengan penggabungan string, data berbahaya yang masuk ke database lokal melalui API dapat menyebabkan SQLi saat pemrosesan selanjutnya.
Database SQLite lokal di perangkat juga rentan terhadap SQL Injection jika aplikasi membentuk permintaan dengan penggabungan string. Content Providers di Android dan Core Data di iOS menggunakan parameterisasi secara default, tetapi permintaan mentah memerlukan perhatian pengembang. SQLite tidak mendukung beberapa permintaan melalui titik koma, yang membatasi kemampuan penyerang, tetapi tidak melindungi dari pembacaan data melalui kondisi WHERE. Selalu gunakan selectionArgs di Android dan NSPredicate dengan parameter di iOS.
API yang diakses aplikasi mobile sama rentannya dengan server web mana pun. Pengembang mobile sering menganggap SQLi hanya masalah backend, tetapi kerentanan muncul di tingkat endpoint API yang menerima parameter dari klien. Pembagian tanggung jawab tidak melindungi: jika pengembang backend lupa memparameterisasi permintaan, aplikasi mobile pengguna menjadi vektor serangan. Minta backend untuk menggunakan ORM atau prepared statements.
// Contoh SQL Injection di SQLite lokal di Android
// KODE RENTAN: penggabungan langsung
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// VERSI AMAN: parameterisasi melalui selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
Perlindungan terhadap SQL Injection didasarkan pada prinsip sederhana: jangan pernah percaya pada input pengguna dalam permintaan SQL. Satu-satunya metode yang dapat diandalkan — parameterisasi permintaan (prepared statements), di mana kode SQL dan data dikirim secara terpisah. Semua metode lain — escaping, validasi, WAF — adalah lapisan perlindungan tambahan, tetapi tidak menggantikan parameterisasi. Menurut data OWASP (2025), parameterisasi mencegah 100% serangan SQL Injection.
Saat menggunakan prepared statements, permintaan SQL pertama-tama dikompilasi oleh server database tanpa data, kemudian nilai parameter dikirim secara terpisah. Database memperlakukan parameter sebagai data, bukan sebagai kode yang dapat dieksekusi. Bahkan jika penyerang mengirim ' OR '1'='1, database menafsirkannya sebagai nilai string, bukan kode SQL. Prepared statements didukung oleh semua bahasa dan framework modern: PDO di PHP, PreparedStatement di Java, cursor.execute di Python.
ORM modern (Hibernate, Entity Framework, SQLAlchemy, Room) secara otomatis menggunakan parameterisasi saat menjalankan permintaan, kecuali jika pengembang beralih ke permintaan mentah. Namun, ORM tidak melindungi sepenuhnya: konstruksi seperti @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) di JPA memerlukan pengiriman parameter melalui parameter bernama, bukan penggabungan. Query Builder (Knex, jOOQ) juga memparameterisasi permintaan secara default, jika metode mentah tidak digunakan.
Escaping karakter khusus (mysql_real_escape_string) — metode usang yang tidak melindungi dari semua jenis SQL Injection. Masalah: escaping tergantung pada pengkodean dan dapat dilewati dengan menggunakan pengkodean multi-byte (misalnya, GBK di sistem Asia). Gunakan escaping hanya dalam kode legacy di mana parameterisasi tidak memungkinkan, dan selalu dikombinasikan dengan validasi ketat tipe data input.
| Metode | Efektivitas | Rekomendasi |
|---|---|---|
| Prepared Statements | 100% | Wajib untuk semua permintaan |
| ORM (penggunaan benar) | 99% | Direkomendasikan |
| Escaping string | 70% (tergantung pengkodean) | Hanya legacy |
| Validasi input (daftar putih) | 50% (hanya untuk angka) | Tambahan |
| WAF (Web Application Firewall) | 60% | Tambahan |
Akun aplikasi harus memiliki hak minimal yang diperlukan: SELECT, INSERT, UPDATE, DELETE — hanya pada tabel yang benar-benar dibutuhkan aplikasi. Larang penggunaan DROP, TRUNCATE, CREATE untuk akun aplikasi. Ini akan membatasi kerusakan bahkan jika SQL Injection berhasil: penyerang tidak akan dapat menghapus tabel atau menjalankan operasi administratif.
Pengujian rutin untuk SQL Injection harus menjadi bagian dari pipeline pengembangan aman. Kombinasi analisis statis, pemindaian dinamis, dan pengujian penetrasi manual memberikan hasil terbaik. Menurut Synopsys Cybersecurity Report (2025), pemindai otomatis menemukan hingga 70% kerentanan SQLi, tetapi serangan Blind yang kompleks memerlukan pengujian manual.
Untuk aplikasi mobile, analisis SQLite lokal juga penting: periksa semua panggilan rawQuery, permintaan ContentProvider, dan permintaan Room dengan rawQuery. Alat: Android Studio Lint (mendeteksi SQLi di SQLite), MobSF (Mobile Security Framework) untuk analisis statis dan dinamis otomatis APK/IPA. Disarankan juga untuk menguji endpoint API melalui sqlmap dengan proxy penyadap lalu lintas aplikasi mobile.
Pertanyaan yang sering diajukan
SQL Injection menyerang database relasional melalui permintaan SQL. NoSQL Injection memengaruhi database non-relasional (MongoDB, Couchbase) melalui operator permintaan mereka ($gte, $ne, $where). Di MongoDB, injeksi dimungkinkan jika aplikasi membentuk dokumen BSON dari string JSON. Mekanisme perlindungan serupa: parameterisasi dan validasi tipe.
Temukan semua tempat di mana permintaan SQL dibentuk melalui penggabungan string dengan data pengguna. Cari pola seperti "SELECT ... WHERE id = " + userId atau f"UPDATE ... SET name = '{name}'". Setiap string tersebut adalah potensi injeksi SQL. Ganti semuanya dengan permintaan terparameterisasi atau prepared statements.
Framework ORM melindungi secara otomatis hanya jika Anda menggunakan metode Query Builder dan parameter bernama. Jika Anda menggunakan permintaan mentah (nativeQuery di JPA, rawQuery di Room), perlindungan tidak berfungsi — Anda harus mengirim parameter melalui ekspresi yang disiapkan, bukan melalui penggabungan string.
Second-Order SQL Injection adalah serangan di mana data berbahaya disimpan di database sebagai aman tetapi kemudian digunakan dalam permintaan lain tanpa escaping. Misalnya, penyerang mendaftar dengan username ' OR '1'='1. Data disimpan sebagai string — saat pendaftaran tidak ada serangan. Tetapi jika permintaan lain mensubstitusi username ke SQL tanpa parameterisasi, injeksi akan aktif.
NoSQL Injection berpotensi lebih berbahaya karena kesadaran pengembang yang lebih rendah. Pengembang tahu tentang SQL Injection dan sebagian besar menggunakan ORM, tetapi hanya sedikit yang tahu tentang NoSQL Injection. Di MongoDB, permintaan yang salah bentuk dapat mengembalikan semua dokumen koleksi. Perlindungan — prepared statements yang sama (parameterisasi BSON) dan validasi ketat data input.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga