flawopen.com/SQL Injection/Python

SQL Injection di Python

Kritis CWE-89 Draf — menunggu tinjauan
Language: English Deutsch Español Français हिन्दी Bahasa Indonesia 日本語 한국어 Português (Brasil) Русский 简体中文
Penjelasan sederhana

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.

Istilah kunci di halaman ini
input yang dikendalikan pengguna
Nilai apa pun yang pada akhirnya berasal dari orang yang menggunakan — atau menyerang — aplikasi: kolom formulir, parameter URL, nama file yang diunggah, header HTTP. Aplikasi tidak boleh berasumsi bahwa nilai tersebut valid atau aman.
kueri SQL
Perintah yang dikirim ke basis data — misalnya "ambil baris ini", "hapus tabel ini". Maknanya sepenuhnya ditentukan oleh teks persisnya, dan itulah yang membuat penyisipan teks tambahan ke dalamnya berbahaya.

Apa yang terjadi

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.

Dampak di dunia nyata

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.

Rentan vs. diperbaiki

RENTAN
# 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()
DIPERBAIKI
# 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()

Mengapa perbaikan ini berhasil

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.

Hal-hal khas Python

Pemformatan % di dalam execute() masih terlihat seperti parameterisasi — padahal bukan

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 sendiricursor.execute("...WHERE id = %s", (user_id,)) — sehingga driver, bukan pemformatan string Python, yang melakukan substitusi.

ORM melakukan parameterisasi secara default, tetapi jalur pintasnya tidak

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.

Sintaks placeholder tidak konsisten antar-driver

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.

Kesalahpahaman umum

"ORM saya otomatis melindungi saya"

Benar untuk API kueri normal ORM — salah begitu Anda menggunakan raw() atau text() dan menyusun string itu sendiri.

"ID ini selalu berupa angka, jadi aman untuk disisipkan langsung"

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.

"Saya sudah meng-escape tanda kutip sendiri, jadi tidak perlu kueri terparameterisasi"

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.

Cara memeriksa apakah Anda terdampak

grep -rn "execute(f\"" --include="*.py" . grep -rn "execute(.*%\s*(" --include="*.py" . grep -rn "\.raw(\|text(" --include="*.py" .
Lebih baik daripada sekadar grep: jalankan Bandit (aturan B608, hardcoded_sql_expressions) di CI — alat ini mendeteksi pola ini secara otomatis dan menggagalkan build saat ada kemunculan baru.

Daftar periksa pencegahan

Pertanyaan yang sering diajukan

Apakah menggunakan ORM melindungi saya dari SQL injection?

Untuk metode kueri normalnya, ya. Jalur pintas kueri mentahnya, tidak — jalur itu sama amannya dengan SQL yang ditulis manual, tidak lebih.

Apakah risiko ini hanya ada pada hal seperti kotak pencarian?

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.

Bolehkah saya cukup meng-escape tanda kutip sendiri?

Boleh, tetapi rapuh dan bergantung pada driver. Kueri terparameterisasi adalah perbaikan sesungguhnya, bukan versi escaping yang lebih ketat.

Referensi

Lihat dalam: Python JavaScriptGoJava PHPC#Ruby C/C++RustKotlin Swift Solidity (T/A)
Lihat juga: Command InjectionPath Traversal XSSDeserialisasi Tidak Aman