flawopen.com/SQL Injection/Python
Bayangkan sebuah formulir yang hanya mengharapkan nomor tiket, seperti 482. SQL injection terjadi saat seseorang mengetik sesuatu yang licik di kotak itu, bukan angka — sebuah kalimat jebakan yang membuat sistem menjawab "tampilkan semua tiket" alih-alih hanya tiket 482, karena sistem tidak pernah memeriksa apakah yang diterimanya benar-benar hanya angka.
SQL injection terjadi ketika input yang dikendalikan pengguna dimasukkan langsung ke dalam teks kueri basis data, alih-alih diteruskan sebagai nilai terpisah. Jika kode menyusun kueri dengan menggabungkan string, penyerang dapat memberikan input yang mengubah struktur asli kueri — mengubah kueri "ambil satu baris" menjadi kueri yang mengembalikan semua baris, atau bahkan menghapus sebuah tabel.
Di Python, hal ini hampir selalu muncul dengan cara yang sama: pemanggilan basis data yang dibangun dengan f-string, pemformatan %, atau penggabungan sederhana dengan +, alih-alih menggunakan placeholder parameter bawaan driver basis data.
Pada 2015, operator telekomunikasi Inggris TalkTalk mengalami kebocoran data yang memengaruhi lebih dari 150.000 pelanggan setelah penyerang mengeksploitasi celah SQL injection pada halaman web lama yang diwarisi dari akuisisi perusahaan. Regulator perlindungan data Inggris mendenda TalkTalk £400.000, menyebut kegagalan tersebut "dapat dicegah dan sangat mendasar".
Sumber: pemberitahuan penegakan dari Information Commissioner's Office Inggris, 2016 — lihat Referensi di bawah.# user_id langsung berasal dari request
def get_user(cursor, user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
return cursor.fetchone()
# nilai diteruskan terpisah, tidak pernah disisipkan
def get_user(cursor, user_id):
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
return cursor.fetchone()
Versi yang diperbaiki meneruskan teks kueri dan nilai ke execute() sebagai dua argumen terpisah. Driver basis data juga mengirimkannya secara terpisah ke basis data — struktur kueri sudah ditetapkan sebelum nilai dilampirkan padanya, sehingga nilai tersebut tidak pernah bisa ditafsirkan sebagai bagian dari sintaks SQL, apa pun karakter yang dikandungnya. F-string tidak bisa melakukan ini: pada saat execute() menerima kueri, nilai tersebut sudah terpanggang ke dalam teks seolah-olah selalu menjadi bagian dari perintah itu.
cursor.execute("... WHERE id = %s" % user_id) sama rentannya dengan f-string. Placeholder baru menjadi aman ketika nilai diteruskan sebagai argumen kedua dari execute() itu sendiri — cursor.execute("...WHERE id = %s", (user_id,)) — sehingga driver, bukan pemformatan string Python, yang melakukan substitusi.
ORM Django dan query builder SQLAlchemy melakukan parameterisasi otomatis untuk kueri normal. Risikonya kembali muncul begitu Anda menggunakan Model.objects.raw() atau text() milik SQLAlchemy dan menyusun SQL mentah itu dengan f-string.
psycopg2 (PostgreSQL) menggunakan %s terlepas dari tipe kolom; sqlite3 menggunakan ?. Menyalin gaya placeholder dari dokumentasi satu driver ke driver lain akan gagal secara diam-diam — periksa gaya parameter driver spesifik Anda, jangan berasumsi.
Benar untuk API kueri normal ORM — salah begitu Anda menggunakan raw() atau text() dan menyusun string itu sendiri.
Risikonya bukan pada tipe nilai saat runtime — risikonya ada pada fakta bahwa kueri itu sendiri dibangun melalui interpolasi string. Begitu asumsi itu gagal di titik mana pun dalam siklus hidup kode, kerentanan sudah menunggu di sana.
Escaping manual bergantung pada driver dan mudah salah dengan cara yang halus. Kueri terparameterisasi bukan versi escaping yang lebih ketat — kueri ini menghindari masalah sepenuhnya, karena nilai tidak pernah menjadi bagian dari teks kueri.
grep -rn "execute(f\"" --include="*.py" .
grep -rn "execute(.*%\s*(" --include="*.py" .
grep -rn "\.raw(\|text(" --include="*.py" .
Untuk metode kueri normalnya, ya. Jalur pintas kueri mentahnya, tidak — jalur itu sama amannya dengan SQL yang ditulis manual, tidak lebih.
Tidak. Apa pun yang secara efektif dikendalikan oleh pihak luar termasuk di dalamnya — header HTTP, nama file yang diunggah, bahkan nilai dari API pihak ketiga yang dipercaya aplikasi Anda.
Boleh, tetapi rapuh dan bergantung pada driver. Kueri terparameterisasi adalah perbaikan sesungguhnya, bukan versi escaping yang lebih ketat.