Mengapa tidak membuat kueri non-parameterisasi mengembalikan kesalahan?

22

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?

Mason Wheeler
sumber
22
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.
amon
6
Bagaimana dengan memilih catatan dari hari iniSELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee
10
@MasonWheeler maaf, maksud saya “coba izinkan itu”. Perhatikan bahwa parameter sempurna dan tidak menderita injeksi SQL. Namun, pengandar basis data tidak dapat mengetahui apakah literal "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).
amon
4
dan bagaimana RDBMS mendeteksi apakah akan melakukan ini? Semalam akan membuatnya tidak mungkin untuk mengakses RDBMS menggunakan SQL prompt interaktif ... Anda tidak lagi dapat memasukkan perintah DDL atau DML menggunakan alat apa pun.
jwenting
8
Dalam arti tertentu Anda dapat melakukan ini: jangan membuat kueri SQL saat runtime sama sekali, alih-alih gunakan ORM atau lapisan abstraksi lain yang menghindari Anda perlu membuat kueri SQL. ORM tidak memiliki fitur yang Anda butuhkan? Kemudian SQL adalah bahasa yang ditujukan untuk orang-orang yang ingin menulis SQL, itulah sebabnya secara keseluruhan memungkinkan mereka menulis SQL. Masalah mendasarnya adalah bahwa menghasilkan kode secara dinamis lebih sulit daripada yang terlihat, tetapi orang tetap ingin melakukannya dan akan tidak puas dengan produk yang tidak membiarkannya.
Steve Jessop

Jawaban:

45

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

SELECT *
  FROM bug
 WHERE status = 'active'

daripada melewati statussebagai variabel pengikat. Saya ingin rencana kueri yang berbeda tergantung pada nilai yang diteruskan untuk status- Saya ingin melakukan pemindaian tabel untuk mengembalikan bug yang ditutup dan pemindaian indeks padastatuskolom 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_codekolom yang memiliki kode pos 5 karakter dan terkadang memiliki 4 digit tambahan, masuk akal untuk melakukan sesuatu seperti

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

daripada 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.

Gua Justin
sumber
12

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.

supercat
sumber
1
Sebagai perkiraan untuk "apakah ini literal", Anda dapat memeriksa apakah string diinternir.
CodesInChaos
1
@CodesInChaos: Benar, dan pengujian semacam itu mungkin cukup akurat untuk tujuan ini, asalkan siapa pun yang memiliki alasan untuk menghasilkan string saat runtime menggunakan metode yang menerima string non-literal daripada menginternir string yang dihasilkan runtime dan menggunakan that (memberikan metode non-literal-string nama yang berbeda akan membuatnya mudah bagi peninjau kode untuk memeriksa semua penggunaannya).
supercat
Perhatikan bahwa sementara tidak ada cara untuk melakukan ini dalam C #, beberapa bahasa lain memiliki fasilitas yang memungkinkan (misalnya modul string tercemar Perl).
Jules
Lebih singkatnya, ini adalah masalah klien , bukan masalah server.
Blrfl
7
SELECT count(ID)
FROM posts
WHERE deleted = false

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 varcharsuntuk 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.

ratchet freak
sumber
2
Jika solusi Anda untuk "terlalu mudah untuk dilupakan - atau tidak tahu sejak awal - untuk menggunakan pertanyaan parametrized" adalah "membuat semua orang ingat - dan tahu di tempat pertama - untuk menggunakan procs yang disimpan", maka Anda hilang seluruh poin dari pertanyaan.
Mason Wheeler
5
Saya telah melihat injeksi SQL melalui prosedur tersimpan di tempat kerja saya. Ternyata mandat prosedur tersimpan untuk semuanya BURUK. Selalu ada 0,5% yang merupakan kueri dinamis yang sebenarnya (Anda tidak dapat menentukan seluruh klausa mana, apalagi sebuah tabel bergabung).
Joshua
Dalam contoh di jawaban ini Anda dapat mengganti deleted = falsedengan NOT deleted, yang menghindari literal. Tetapi intinya berlaku secara umum.
psmears
5

TL; DR : Anda harus membatasi semua literal, bukan hanya yang ada dalam WHEREklausa. Untuk alasan mengapa tidak, ini memungkinkan basis data untuk tetap dipisahkan dari sistem lain.

Pertama, premis Anda cacat. Anda ingin membatasi hanya WHEREklausa, tetapi itu bukan satu-satunya tempat input pengguna dapat pergi. Sebagai contoh,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Ini sama-sama rentan terhadap injeksi SQL:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Jadi Anda tidak bisa hanya membatasi literal dalam WHEREklausa. 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:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

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:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

Mencoba melakukan itu akan menjadi mimpi buruk pemeliharaan, terutama karena sintaksis yang valid sering berubah antara rilis utama dari basis data.

jpmc26
sumber