Injeksi SQL adalah masalah keamanan yang sangat serius, sebagian besar karena sangat mudah untuk mendapatkannya salah: cara yang jelas dan intuitif untuk membangun kueri yang memasukkan input pengguna membuat Anda rentan, dan Cara yang Benar untuk memitigasinya mengharuskan Anda untuk mengetahui tentang parameter pertanyaan dan injeksi SQL terlebih dahulu.
Tampak bagi saya bahwa cara yang jelas untuk memperbaikinya adalah dengan mematikan opsi yang jelas (tapi salah): perbaiki mesin basis data sehingga setiap kueri yang diterima yang menggunakan nilai-nilai yang dikodekan dalam klausa WHERE-nya alih-alih parameter mengembalikan parameter yang bagus, deskriptif pesan kesalahan yang memerintahkan Anda untuk menggunakan parameter sebagai gantinya. Ini jelas perlu memiliki opsi opt-out sehingga hal-hal seperti permintaan ad-hoc dari alat administratif akan tetap berjalan dengan mudah, tetapi harus diaktifkan secara default.
Memiliki ini akan mematikan injeksi SQL dingin, hampir semalam, tetapi sejauh yang saya tahu, tidak ada RDBMS yang benar-benar melakukan ini. Apakah ada alasan bagus mengapa tidak?
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'
akan memiliki nilai-nilai hard-kode dan parameter dalam satu permintaan - coba tangkap itu! Saya pikir ada kasus penggunaan yang valid untuk kueri campuran tersebut.SELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
"bad"
benar-benar literal atau hasil dari penggabungan string. Dua solusi yang saya lihat adalah menyingkirkan SQL dan DSL yang tersemat string lainnya (ya silakan), atau mempromosikan bahasa di mana penggabungan string lebih mengganggu daripada menggunakan kueri parameterisasi (umm, tidak).Jawaban:
Ada terlalu banyak kasus di mana menggunakan literal adalah pendekatan yang tepat.
Dari sudut pandang kinerja, ada saatnya Anda ingin literal dalam kueri Anda. Bayangkan saya memiliki pelacak bug di mana setelah itu menjadi cukup besar untuk khawatir tentang kinerja saya berharap bahwa 70% dari bug dalam sistem akan "ditutup", 20% akan "terbuka", 5% akan "aktif" dan 5 % akan berada dalam status lain. Saya mungkin ingin memiliki kueri yang mengembalikan semua bug aktif menjadi
daripada melewati
status
sebagai variabel pengikat. Saya ingin rencana kueri yang berbeda tergantung pada nilai yang diteruskan untukstatus
- Saya ingin melakukan pemindaian tabel untuk mengembalikan bug yang ditutup dan pemindaian indeks padastatus
kolom untuk mengembalikan pinjaman aktif. Sekarang, basis data yang berbeda dan versi yang berbeda memiliki pendekatan yang berbeda untuk (kurang lebih berhasil) memungkinkan permintaan yang sama untuk menggunakan rencana permintaan yang berbeda tergantung pada nilai dari variabel mengikat. Tapi itu cenderung untuk memperkenalkan sejumlah kompleksitas yang layak yang perlu dikelola untuk menyeimbangkan keputusan apakah akan mengganggu penguraian kembali kueri atau apakah akan menggunakan kembali rencana yang ada untuk nilai variabel binding baru. Untuk pengembang, mungkin masuk akal untuk menangani kompleksitas ini. Atau mungkin masuk akal untuk memaksakan jalur yang berbeda ketika saya memiliki lebih banyak informasi tentang seperti apa data saya akan terlihat daripada pengoptimal.Dari sudut pandang kompleksitas kode, ada juga banyak waktu yang masuk akal untuk memiliki literal dalam pernyataan SQL. Misalnya, jika Anda memiliki
zip_code
kolom yang memiliki kode pos 5 karakter dan terkadang memiliki 4 digit tambahan, masuk akal untuk melakukan sesuatu sepertidaripada melewati 4 parameter terpisah untuk nilai numerik. Ini bukan hal-hal yang akan berubah sehingga membuatnya mengikat variabel hanya berfungsi untuk membuat kode berpotensi lebih sulit dibaca dan untuk membuat potensi seseorang mengikat parameter dalam urutan yang salah dan berakhir dengan bug.
sumber
Injeksi SQL terjadi ketika kueri dibuat dengan menggabungkan teks dari sumber yang tidak tepercaya dan tidak divalidasi dengan bagian lain dari kueri. Sementara hal seperti itu paling sering terjadi dengan string literal, itu bukan satu-satunya cara itu bisa terjadi. Kueri untuk nilai numerik mungkin mengambil string yang dimasukkan pengguna (yang seharusnya hanya berisi digit) dan digabungkan dengan materi lain untuk membentuk kueri tanpa tanda kutip yang biasanya dikaitkan dengan string literal; kode yang terlalu memercayai validasi sisi klien mungkin memiliki hal-hal seperti nama bidang berasal dari string kueri HTML. Tidak ada kode cara melihat string kueri SQL dapat melihat bagaimana itu dirakit.
Yang penting bukanlah apakah pernyataan SQL berisi string literal, melainkan apakah string berisi urutan karakter apa pun dari sumber yang tidak terpercaya , dan validasi untuk itu akan lebih baik ditangani di perpustakaan yang membuat kueri. Pada umumnya tidak ada cara di C # untuk menulis kode yang akan memungkinkan string literal tetapi tidak akan mengizinkan jenis ekspresi string lainnya, tetapi orang dapat memiliki aturan praktik pengkodean yang mengharuskan kueri dibangun menggunakan kelas pembuatan kueri daripada penggabungan string, dan siapa pun yang meneruskan string non-literal ke pembuat kueri harus membenarkan tindakan tersebut.
sumber
Jika Anda ingin meletakkan hasil ini di footer forum Anda, Anda perlu menambahkan parameter dummy hanya untuk mengatakan false setiap kali. Atau programmer web yang naif mencari cara menonaktifkan peringatan itu dan kemudian melanjutkan.
Sekarang Anda dapat mengatakan Anda akan menambahkan pengecualian untuk enum tetapi itu hanya membuka lubang lagi (meskipun lebih kecil). Belum lagi orang harus dididik terlebih dahulu untuk tidak menggunakan
varchars
untuk mereka.Masalah sebenarnya dari injeksi adalah pemrograman membangun string kueri. Solusi untuk itu adalah mekanisme prosedur tersimpan dan menegakkan penggunaannya atau daftar putih kueri yang diizinkan.
sumber
deleted = false
denganNOT deleted
, yang menghindari literal. Tetapi intinya berlaku secara umum.TL; DR : Anda harus membatasi semua literal, bukan hanya yang ada dalam
WHERE
klausa. Untuk alasan mengapa tidak, ini memungkinkan basis data untuk tetap dipisahkan dari sistem lain.Pertama, premis Anda cacat. Anda ingin membatasi hanya
WHERE
klausa, tetapi itu bukan satu-satunya tempat input pengguna dapat pergi. Sebagai contoh,Ini sama-sama rentan terhadap injeksi SQL:
Jadi Anda tidak bisa hanya membatasi literal dalam
WHERE
klausa. Anda harus membatasi semua literal.Sekarang kita dibiarkan dengan pertanyaan, "Mengapa mengizinkan literal sama sekali?" Ingatlah ini: sementara database relasional digunakan di bawah aplikasi yang ditulis dalam bahasa lain dalam persentase besar, tidak ada persyaratan bahwa Anda harus menggunakan kode aplikasi untuk menggunakan database. Dan di sini kita punya jawaban: Anda perlu literal untuk menulis kode. Satu-satunya alternatif lain adalah mewajibkan semua kode ditulis dalam beberapa bahasa terlepas dari basis data. Jadi memiliki mereka memberi Anda kemampuan untuk menulis "kode" (SQL) langsung di database. Ini adalah pemisahan yang berharga, dan tidak mungkin tanpa literal. (Cobalah menulis dalam bahasa favorit Anda kadang-kadang tanpa literal. Saya yakin Anda bisa membayangkan betapa sulitnya ini.)
Sebagai contoh umum, literal sering digunakan dalam populasi tabel daftar nilai / pencarian:
Tanpa mereka, Anda perlu menulis kode dalam bahasa pemrograman lain hanya untuk mengisi tabel ini. Kemampuan untuk melakukannya secara langsung dalam SQL adalah berharga .
Kami kemudian pergi dengan satu pertanyaan lagi: mengapa perpustakaan bahasa klien pemrograman tidak melakukannya? Dan di sini kami memiliki jawaban yang sangat sederhana: mereka akan menerapkan kembali seluruh parser basis data untuk setiap versi database yang didukung . Mengapa? Karena tidak ada cara lain untuk menjamin Anda menemukan setiap literal. Ekspresi reguler tidak cukup. Sebagai contoh: ini mengandung 4 literal terpisah di PostgreSQL:
Mencoba melakukan itu akan menjadi mimpi buruk pemeliharaan, terutama karena sintaksis yang valid sering berubah antara rilis utama dari basis data.
sumber