Cara saya melihatnya, serangan injeksi SQL dapat dicegah dengan:
- Menyaring, memfilter, menyandikan input dengan hati-hati (sebelum dimasukkan ke dalam SQL)
- Menggunakan pernyataan / kueri parameter yang disiapkan
Saya kira ada pro dan kontra untuk masing-masing, tetapi mengapa # 2 lepas landas dan dianggap lebih atau kurang cara de facto untuk mencegah serangan injeksi? Apakah hanya lebih aman dan kurang rentan terhadap kesalahan atau ada faktor lain?
Seperti yang saya mengerti, jika # 1 digunakan dengan benar dan semua peringatan diurus, itu bisa sama efektifnya dengan # 2.
Sanitasi, Penyaringan, dan Pengkodean
Ada beberapa kebingungan di antara saya tentang apa arti sanitasi , penyaringan , dan pengodean . Saya akan mengatakan bahwa untuk tujuan saya, semua hal di atas dapat dipertimbangkan untuk opsi 1. Dalam hal ini saya mengerti bahwa sanitasi dan penyaringan memiliki potensi untuk memodifikasi atau membuang data input, sementara penyandian mempertahankan data apa adanya , tetapi menyandikannya benar untuk menghindari serangan injeksi. Saya percaya bahwa melarikan diri data dapat dianggap sebagai cara penyandian data.
Kueri Parameterisasi vs Perpustakaan Pengkodean
Ada jawaban di mana konsep parameterized queries
dan encoding libraries
diperlakukan secara bergantian. Koreksi saya jika saya salah, tetapi saya mendapat kesan bahwa mereka berbeda.
Pemahaman saya adalah bahwa encoding libraries
, tidak peduli seberapa baik mereka selalu memiliki potensi untuk memodifikasi SQL "Program", karena mereka membuat perubahan pada SQL itu sendiri, sebelum dikirim ke RDBMS.
Parameterized queries
di sisi lain, kirim program SQL ke RDBMS, yang kemudian mengoptimalkan kueri, menentukan rencana eksekusi kueri, memilih indeks yang akan digunakan, dll., dan kemudian memasukkan data, sebagai langkah terakhir di dalam RDBMS diri.
Perpustakaan Pengkodean
data -> (encoding library)
|
v
SQL -> (SQL + encoded data) -> RDBMS (execution plan defined) -> execute statement
Permintaan Parameter
data
|
v
SQL -> RDBMS (query execution plan defined) -> data -> execute statement
Signifikansi Historis
Beberapa jawaban menyebutkan bahwa secara historis, kueri parameterisasi (PQ) dibuat karena alasan kinerja, dan sebelum serangan injeksi yang menargetkan masalah pengodean menjadi populer. Pada titik tertentu menjadi jelas bahwa PQ juga cukup efektif terhadap serangan injeksi. Untuk menjaga semangat pertanyaan saya, mengapa PQ tetap menjadi metode pilihan dan mengapa metode ini berkembang di atas sebagian besar metode lain dalam hal mencegah serangan injeksi SQL?
Jawaban:
Masalahnya adalah bahwa # 1 mengharuskan Anda secara efektif mengurai dan menafsirkan keseluruhan varian SQL yang sedang Anda lawan sehingga Anda tahu jika itu melakukan sesuatu yang seharusnya tidak. Dan perbarui kode itu saat Anda memperbarui basis data Anda. Di mana-mana Anda menerima input untuk pertanyaan Anda. Dan tidak mengacaukannya.
Jadi ya, hal semacam itu akan menghentikan serangan injeksi SQL, tetapi bukan kepalang lebih mahal untuk diterapkan.
sumber
null
, string atau angka dan bertindak sesuai dengannya. Ini sangat baik untuk keamanan. Dan bahkan jika Anda menjalankan kueri sekali, mesin DB sudah akan dioptimalkan untuk Anda. Lebih baik lagi jika di-cache!Karena opsi 1 bukanlah solusi. Penyaringan dan penyaringan berarti menolak atau menghapus input yang tidak valid. Tetapi input apa pun mungkin valid. Misalnya apostrof adalah karakter yang valid dalam nama "O'Malley". Itu hanya harus dikodekan dengan benar sebelum digunakan dalam SQL, yang adalah apa yang disiapkan pernyataan.
Setelah Anda menambahkan catatan itu, tampaknya pada dasarnya Anda bertanya mengapa menggunakan fungsi pustaka standar daripada menulis kode Anda sendiri yang secara fungsional serupa dari awal? Anda harus selalu memilih solusi pustaka standar daripada menulis kode Anda sendiri. Itu kurang bekerja dan lebih bisa dipelihara. Ini adalah kasus untuk fungsionalitas apa pun , tetapi terutama untuk sesuatu yang sensitif terhadap keamanan, sama sekali tidak masuk akal untuk menciptakan kembali roda sendiri.
sumber
O\'Malley
menggunakan garis miring untuk menghindari kutipan untuk penyisipan yang tepat (setidaknya di beberapa database). Dalam MS SQL atau Access dapat diloloskan dengan kutipan tambahanO''Malley
. Tidak terlalu portabel jika Anda harus melakukannya sendiri.Jika Anda mencoba melakukan pemrosesan string, maka Anda tidak benar-benar menghasilkan query SQL. Anda menghasilkan string yang dapat menghasilkan kueri SQL. Ada tingkat tipuan yang membuka banyak ruang untuk kesalahan dan bug. Agak mengherankan, mengingat dalam sebagian besar konteks kami senang berinteraksi dengan sesuatu yang terprogram. Misalnya, jika kita memiliki beberapa struktur daftar dan ingin menambahkan item, biasanya kita tidak melakukannya:
Jika seseorang menyarankan melakukan itu, Anda akan merespons dengan benar bahwa itu agak konyol, dan yang harus dilakukan:
Itu berinteraksi dengan struktur data pada tingkat konseptualnya. Itu tidak memperkenalkan ketergantungan pada bagaimana struktur itu bisa dicetak atau diuraikan. Itu adalah keputusan yang sepenuhnya ortogonal.
Pendekatan pertama Anda seperti contoh pertama (hanya sedikit lebih buruk): Anda mengasumsikan bahwa secara sistematis dapat membangun string yang akan diuraikan dengan benar sebagai kueri yang Anda inginkan. Itu tergantung pada parser, dan sejumlah besar logika pemrosesan string.
Pendekatan kedua menggunakan kueri disiapkan jauh lebih mirip dengan sampel kedua. Saat Anda menggunakan kueri yang disiapkan, Anda pada dasarnya mem-parsing kueri-semu yang legal tetapi memiliki beberapa placeholder di dalamnya, dan kemudian menggunakan API untuk mengganti dengan benar beberapa nilai di sana. Anda tidak lagi melibatkan proses penguraian, dan Anda tidak perlu khawatir tentang pemrosesan string.
Secara umum, jauh lebih mudah, dan jauh lebih sedikit kesalahan, untuk berinteraksi dengan hal-hal pada tingkat konseptual mereka. Kueri bukan string, kueri adalah apa yang Anda dapatkan saat mengurai string, atau membuat satu secara terprogram (atau metode apa pun yang memungkinkan Anda membuatnya).
Ada analogi yang baik di sini antara makro gaya C yang melakukan penggantian teks sederhana dan makro gaya Lisp yang melakukan pembuatan kode arbitrer. Dengan makro gaya-C, Anda dapat mengganti teks dalam kode sumber, dan itu berarti bahwa Anda memiliki kemampuan untuk memperkenalkan kesalahan sintaksis atau perilaku menyesatkan. Dengan Lisp macro, Anda membuat kode dalam bentuk yang dikompilasi oleh kompilernya (yaitu, Anda mengembalikan struktur data aktual yang diproses oleh kompiler, bukan teks yang harus diproses pembaca sebelum kompiler dapat melakukannya) . Dengan makro Lisp, Anda tidak bisa menghasilkan sesuatu yang bisa menjadi kesalahan parse. Misalnya, Anda tidak dapat menghasilkan (biarkan ((ab)) a .
Bahkan dengan macro Lisp, Anda masih dapat menghasilkan kode yang buruk, karena Anda tidak perlu menyadari struktur yang seharusnya ada di sana. Misalnya, dalam Lisp, (let ((ab)) a) berarti "membangun ikatan leksikal baru dari variabel a ke nilai variabel b, dan kemudian mengembalikan nilai a", dan (let (ab) a) berarti + msgstr "buat ikatan leksikal baru dari variabel a dan b dan inisialisasi keduanya menjadi nil, dan kemudian kembalikan nilai a." Keduanya secara sintaksis benar, tetapi keduanya memiliki arti berbeda. Untuk menghindari masalah ini, Anda bisa menggunakan fungsi yang lebih sadar semantik dan melakukan sesuatu seperti:
Dengan sesuatu seperti itu, mustahil untuk mengembalikan sesuatu yang secara sintaksis tidak valid, dan jauh lebih sulit untuk mengembalikan sesuatu yang secara tidak sengaja bukan yang Anda inginkan.
sumber
Ini membantu bahwa opsi # 2 umumnya dianggap sebagai praktik terbaik karena database dapat men-cache versi query yang tidak dikategorikan. Kueri parameterisasi mendahului masalah injeksi SQL oleh beberapa tahun (saya percaya), kebetulan Anda dapat membunuh dua burung dengan satu batu.
sumber
Sederhananya: Mereka tidak. Pernyataan Anda:
secara mendasar cacat. Kueri parameterisasi telah ada jauh lebih lama daripada SQL Injection setidaknya diketahui secara luas. Mereka umumnya dikembangkan sebagai cara untuk menghindari konsentrasi string dalam fungsi "form for search" LOB (Line of Business) aplikasi yang biasa miliki. Banyak - BANYAK - tahun kemudian, seseorang menemukan masalah keamanan dengan manipulasi string tersebut.
Saya ingat melakukan SQL 25 tahun yang lalu (ketika internet TIDAK banyak digunakan - itu baru saja dimulai) dan saya ingat melakukan SQL vs IBM DB5 IIRC versi 5 - dan itu sudah memiliki pertanyaan parameter.
sumber
Selain semua jawaban baik lainnya:
Alasan mengapa # 2 lebih baik adalah karena memisahkan data Anda dari kode Anda. Di # 1 data Anda adalah bagian dari kode Anda dan dari situlah semua hal buruk berasal. Dengan # 1 Anda mendapatkan permintaan Anda dan perlu melakukan langkah-langkah tambahan untuk memastikan permintaan Anda memahami data Anda sebagai data sedangkan di # 2 Anda mendapatkan kode Anda dan itu kode dan data Anda adalah data.
sumber
Permintaan parameter, selain dari menyediakan pertahanan injeksi SQL, sering memiliki manfaat tambahan dikompilasi hanya sekali, kemudian dieksekusi beberapa kali dengan parameter yang berbeda.
Dari sudut pandang SQL database
select * from employees where last_name = 'Smith'
danselect * from employees where last_name = 'Fisher'
jelas berbeda dan karenanya memerlukan parsing, kompilasi, dan optimasi yang terpisah. Mereka juga akan menempati slot terpisah di area memori yang didedikasikan untuk menyimpan pernyataan yang dikompilasi. Dalam sistem yang sarat muatan dengan sejumlah besar kueri serupa yang memiliki parameter perhitungan yang berbeda dan overhead memori bisa sangat besar.Selanjutnya, menggunakan kueri parameterisasi sering memberikan keuntungan kinerja utama.
sumber
prepare
sering sangat berbeda dari tingkat SQL yang sebenarnyaprepare
).SELECT * FROM employees WHERE last_name IN (?, ?)
danSELECT * FROM employees WHERE last_name IN (?, ?, ?, ?, ?, ?)
.Tunggu, mengapa?
Opsi 1 berarti Anda harus menulis rutinitas sanitasi untuk jenis input yang pernah ada sedangkan opsi 2 lebih sedikit rawan kesalahan dan lebih sedikit kode untuk Anda tulis / uji / pertahankan.
Hampir bisa dipastikan "mengurus semua peringatan" bisa jadi lebih rumit dari yang Anda kira, dan bahasa Anda (misalnya Java PreparedStatement) memiliki lebih banyak kekurangan daripada yang Anda pikirkan.
Pernyataan yang disiapkan atau kueri parametrized telah dikompilasi sebelumnya di server database sehingga, ketika parameter ditetapkan, tidak ada penggabungan SQL yang dilakukan karena kueri tidak lagi berupa string SQL. Keuntungan aditional adalah bahwa RDBMS cache kueri dan panggilan berikutnya dianggap SQL yang sama bahkan ketika nilai parameter bervariasi, sedangkan dengan SQL bersambung setiap kali kueri dijalankan dengan nilai yang berbeda kueri berbeda dan RDBMS harus menguraikannya , buat lagi rencana eksekusi, dll.
sumber
Mari kita bayangkan seperti apa pendekatan "sanitasi, filter, dan penyandian" yang ideal.
Sanitasi dan pemfilteran mungkin masuk akal dalam konteks aplikasi tertentu, tetapi pada akhirnya mereka berdua mengatakan "Anda tidak bisa memasukkan data ini ke dalam basis data". Untuk aplikasi Anda, itu mungkin ide yang bagus, tetapi itu bukan sesuatu yang dapat Anda rekomendasikan sebagai solusi umum, karena akan ada aplikasi yang perlu dapat menyimpan karakter sewenang-wenang dalam database.
Sehingga meninggalkan pengkodean. Anda bisa mulai dengan memiliki fungsi yang menyandikan string dengan menambahkan karakter escape, sehingga Anda dapat menggantikannya dalam diri Anda. Karena basis data yang berbeda memerlukan karakter yang berbeda untuk melarikan diri (dalam beberapa basis data, keduanya
\'
dan''
merupakan urutan pelarian yang valid untuk'
, tetapi tidak pada yang lain), fungsi ini perlu disediakan oleh vendor basis data.Tetapi tidak semua variabel adalah string. Terkadang Anda perlu mengganti dalam bilangan bulat, atau tanggal. Ini diwakili secara berbeda untuk string, sehingga Anda memerlukan metode pengkodean yang berbeda (sekali lagi, ini harus spesifik untuk vendor database), dan Anda perlu menggantinya ke dalam kueri dengan cara yang berbeda.
Jadi mungkin hal-hal akan lebih mudah jika database juga menangani substitusi untuk Anda - ia sudah tahu jenis apa yang diharapkan dari kueri, dan bagaimana cara menyandikan data dengan aman, dan bagaimana cara menggantinya dengan permintaan Anda dengan aman, sehingga Anda tidak perlu khawatir tentang itu dalam kode Anda.
Pada titik ini, kami baru saja menemukan kembali query parameterised.
Dan sekali pertanyaan parameter, itu membuka peluang baru, seperti optimasi kinerja, dan pemantauan disederhanakan.
Pengkodean sulit dilakukan dengan benar, dan pengodean-selesai-benar tidak dapat dibedakan dari parameterisasi.
Jika Anda benar-benar seperti interpolasi string sebagai cara query bangunan, ada beberapa bahasa (Scala dan ES2015 datang ke pikiran) yang memiliki pluggable interpolasi tali, sehingga ada yang perpustakaan yang memungkinkan Anda menulis pertanyaan parameterised yang terlihat seperti interpolasi string, tapi aman dari injeksi SQL - jadi dalam sintaks ES2015:
sumber
Dalam opsi 1, Anda bekerja dengan set input size = infinity yang Anda coba petakan ke ukuran output yang sangat besar. Dalam opsi 2, Anda telah membatasi input Anda dengan apa pun yang Anda pilih. Dengan kata lain:
Menurut jawaban lain, tampaknya juga ada beberapa manfaat kinerja dari membatasi ruang lingkup Anda jauh dari tak terbatas dan menuju sesuatu yang dapat dikelola.
sumber
Salah satu model mental SQL yang berguna (terutama dialek modern) adalah bahwa setiap pernyataan atau kueri SQL adalah sebuah program. Dalam program biner asli yang dapat dieksekusi, jenis kerentanan keamanan yang paling berbahaya adalah meluap di mana penyerang dapat menimpa atau memodifikasi kode program dengan instruksi yang berbeda.
Kerentanan injeksi SQL bersifat isomorfik terhadap buffer overflow dalam bahasa seperti C. Sejarah telah menunjukkan bahwa buffer overflows sangat sulit dicegah - bahkan kode yang sangat kritis yang harus ditinjau secara terbuka sering mengandung kerentanan seperti itu.
Salah satu aspek penting dari pendekatan modern untuk mengatasi kerentanan overflow adalah penggunaan perangkat keras dan mekanisme OS untuk menandai bagian tertentu dari memori sebagai tidak dapat dieksekusi, dan untuk menandai bagian lain dari memori sebagai hanya-baca. (Lihat artikel Wikipedia tentang Perlindungan ruang yang dapat dijalankan , misalnya.) Dengan begitu, bahkan jika penyerang dapat mengubah data, penyerang tidak dapat menyebabkan data yang disuntikkan diperlakukan sebagai kode.
Jadi jika kerentanan injeksi SQL setara dengan buffer overflow, lalu apa yang setara dengan SQL untuk NX bit, atau untuk halaman memori read-only? Jawabannya adalah: pernyataan yang disiapkan , yang mencakup kueri berparameterisasi plus mekanisme serupa untuk permintaan non-kueri. Pernyataan yang disiapkan dikompilasi dengan bagian-bagian tertentu yang ditandai hanya baca, sehingga penyerang tidak dapat mengubah bagian-bagian dari program, dan bagian-bagian lain yang ditandai sebagai data yang tidak dapat dieksekusi (parameter dari pernyataan yang disiapkan), dimana penyerang dapat menyuntikkan data ke dalam tetapi yang tidak akan pernah diperlakukan sebagai kode program, sehingga menghilangkan sebagian besar potensi penyalahgunaan.
Tentu saja, membersihkan input pengguna itu baik, tetapi untuk benar-benar aman Anda harus paranoid (atau, setara, untuk berpikir seperti penyerang). Permukaan kontrol di luar teks program adalah cara untuk melakukan itu, dan pernyataan yang disiapkan memberikan permukaan kontrol untuk SQL. Jadi seharusnya tidak mengherankan bahwa pernyataan yang disiapkan, dan dengan demikian pertanyaan parameter, adalah pendekatan yang direkomendasikan sebagian besar profesional keamanan.
sumber
Saya sudah menulis tentang ini di sini: https://stackoverflow.com/questions/6786034/can-parameterized-statement-stop-all-sql-injection/33033576#33033576
Tapi, untuk membuatnya sederhana:
Cara kerja parameterisasi bekerja, adalah bahwa sqlQuery dikirim sebagai kueri, dan basis data tahu persis apa yang akan dilakukan kueri ini, dan hanya kemudian ia akan memasukkan nama pengguna dan kata sandi hanya sebagai nilai. Ini berarti mereka tidak dapat mempengaruhi permintaan, karena database sudah tahu apa yang akan dilakukan permintaan. Jadi dalam hal ini akan mencari nama pengguna "Tidak Ada OR 1 = 1 '-" dan kata sandi kosong, yang akan muncul salah.
Ini bukan solusi yang lengkap, dan validasi input masih perlu dilakukan, karena ini tidak akan mempengaruhi masalah lain, seperti serangan XSS, karena Anda masih bisa memasukkan javascript ke dalam database. Kemudian jika ini dibacakan ke halaman, itu akan menampilkannya sebagai javascript normal, tergantung pada validasi output. Jadi benar-benar hal terbaik untuk dilakukan adalah masih menggunakan input validasi, tetapi menggunakan query parameterized atau prosedur tersimpan untuk menghentikan serangan SQL
sumber
Saya tidak pernah menggunakan SQL. Tapi jelas Anda mendengar tentang masalah apa yang orang miliki, dan pengembang SQL memiliki masalah dengan hal "injeksi SQL" ini. Untuk waktu yang lama saya tidak bisa mengetahuinya. Dan kemudian saya menyadari bahwa orang-orang di mana membuat pernyataan SQL, pernyataan sumber SQL tekstual nyata, dengan merangkai string, di mana beberapa tempat dimasukkan oleh pengguna. Dan pikiran pertama saya tentang kesadaran itu mengejutkan. Kejutan total. Saya berpikir: Bagaimana orang bisa begitu bodoh dan membuat pernyataan dalam bahasa pemrograman seperti itu? Untuk C, atau C ++, atau Java, atau pengembang Swift, ini benar-benar gila.
Yang mengatakan, tidak terlalu sulit untuk menulis fungsi C yang mengambil string C sebagai argumennya, dan menghasilkan string yang berbeda persis seperti string literal dalam kode sumber C yang mewakili string yang sama. Misalnya, fungsi itu akan menerjemahkan abc menjadi "abc", dan "abc" menjadi "\" abc \ "" dan "\" abc \ "" menjadi "\" \\ "abc \\" \ "". (Ya, jika ini terlihat salah bagi Anda, itu html. Itu benar ketika saya mengetiknya, tetapi tidak ketika ditampilkan) Dan begitu fungsi C ditulis, sama sekali tidak sulit untuk menghasilkan kode sumber C di mana teks dari bidang input yang disediakan oleh pengguna diubah menjadi string C literal. Itu tidak sulit untuk dibuat aman. Mengapa pengembang SQL tidak akan menggunakan pendekatan itu sebagai cara untuk menghindari suntikan SQL adalah di luar saya.
"Sanitasi" adalah pendekatan yang benar-benar cacat. Kesalahan fatalnya adalah membuat input pengguna tertentu menjadi ilegal. Anda berakhir dengan database di mana bidang teks generik tidak dapat berisi teks seperti; Drop Table atau apa pun yang Anda gunakan dalam injeksi SQL untuk menyebabkan kerusakan. Saya menemukan itu tidak dapat diterima. Jika suatu database menyimpan teks, ia harus dapat menyimpan teks apa pun . Dan kekurangan praktisnya adalah pembersih itu sepertinya tidak bisa memperbaikinya :-(
Tentu saja, pertanyaan parameter adalah yang diharapkan oleh setiap programmer yang menggunakan bahasa yang dikompilasi. Itu membuat hidup jadi lebih mudah: Anda memiliki beberapa input string, dan Anda bahkan tidak pernah repot-repot menerjemahkannya ke dalam string SQL, tetapi hanya meneruskannya sebagai parameter, tanpa ada kemungkinan karakter dalam string yang menyebabkan kerusakan.
Jadi dari sudut pandang pengembang yang menggunakan bahasa yang dikompilasi, membersihkan adalah sesuatu yang tidak akan pernah terjadi pada saya. Kebutuhan untuk sanitasi itu gila. Pertanyaan parameterised adalah solusi yang jelas untuk masalah ini.
(Saya menemukan jawaban Josip menarik. Dia pada dasarnya mengatakan bahwa dengan query parameterised Anda dapat menghentikan serangan terhadap SQL, tetapi kemudian Anda dapat memiliki teks di database Anda yang digunakan untuk membuat injeksi JavaScript :-( Yah, kami memiliki masalah yang sama lagi , dan saya tidak tahu apakah Javascript memiliki solusi untuk itu.
sumber
Masalah utama adalah bahwa peretas menemukan cara untuk mengelilingi sanitasi sementara pertanyaan parametrized adalah prosedur yang ada yang bekerja dengan sempurna dengan manfaat tambahan dari kinerja dan memori.
Beberapa orang menyederhanakan masalah sebagai "itu hanya kutipan tunggal dan kutipan ganda" tetapi peretas menemukan cara cerdas untuk menghindari deteksi seperti menggunakan pengkodean yang berbeda atau memanfaatkan fungsi basis data.
Lagi pula, Anda hanya perlu melupakan satu string tunggal untuk membuat pelanggaran data katastropik. Peretas di mana dapat mengotomatisasi skrip untuk mengunduh basis data lengkap dengan serangkaian atau kueri. Jika perangkat lunaknya dikenal seperti suite sumber terbuka atau suite bisnis terkenal, Anda cukup menarik pengguna dan tabel kata sandi.
Di sisi lain, hanya menggunakan kueri gabungan hanya masalah belajar menggunakan dan membiasakan diri dengannya.
sumber