it-swarm-id.com

Apakah pernyataan yang disiapkan 100% aman terhadap injeksi SQL?

Apakah pernyataan yang disiapkan sebenarnya 100% aman terhadap injeksi SQL, dengan asumsi semua parameter yang disediakan pengguna dilewatkan sebagai parameter terikat kueri?

Setiap kali saya melihat orang menggunakan yang lama mysql_ fungsi pada StackOverflow (yang, sayangnya, terlalu sering) Saya biasanya memberi tahu orang-orang bahwa pernyataan yang disiapkan adalah Chuck Norris (atau Jon Skeet) dari langkah-langkah keamanan injeksi SQL.

Namun, saya belum pernah benar-benar melihat dokumentasi yang secara kategoris menyatakan "ini 100% aman". Pemahaman saya tentang mereka adalah bahwa mereka memisahkan bahasa query dan parameter sampai ke pintu depan server, yang kemudian memperlakukan mereka sebagai entitas yang terpisah.

Apakah saya benar dalam asumsi ini?

80
Polynomial

Jaminan 100% aman dari injeksi SQL? Tidak akan mendapatkannya (dari saya).

Pada prinsipnya, basis data Anda (atau pustaka dalam bahasa Anda yang berinteraksi dengan db) dapat mengimplementasikan pernyataan yang dipersiapkan dengan parameter terikat dengan cara yang tidak aman rentan terhadap semacam serangan lanjutan, misalnya mengeksploitasi buffer overflows atau memiliki karakter penghentian nol di pengguna- menyediakan string, dll. (Anda bisa berargumen bahwa jenis serangan ini tidak boleh disebut injeksi SQL karena mereka berbeda secara mendasar; tapi itu hanya semantik).

Saya belum pernah mendengar salah satu dari serangan ini pada pernyataan yang disiapkan pada database nyata di lapangan dan sangat menyarankan menggunakan parameter terikat untuk mencegah injeksi SQL. Tanpa parameter terikat atau sanitasi input, itu sepele untuk melakukan injeksi SQL. Dengan hanya sanitasi input, sangat mungkin untuk menemukan celah yang tidak jelas di sekitar sanitasi.

Dengan parameter terikat, rencana eksekusi permintaan SQL Anda diketahui sebelumnya tanpa bergantung pada input pengguna, yang seharusnya membuat injeksi SQL tidak mungkin (karena setiap kutipan yang dimasukkan, simbol komentar, dll. Hanya dimasukkan dalam pernyataan SQL yang sudah dikompilasi).

Satu-satunya argumen yang menentang penggunaan pernyataan yang disiapkan adalah Anda ingin database Anda mengoptimalkan rencana eksekusi Anda tergantung pada permintaan aktual. Sebagian besar basis data ketika diberi kueri lengkap cukup pintar untuk melakukan rencana eksekusi yang optimal; mis., jika kueri mengembalikan sebagian besar tabel, ia ingin menelusuri seluruh tabel untuk menemukan kecocokan; sementara jika hanya akan mendapatkan beberapa catatan, Anda dapat melakukan pencarian berbasis indeks [1] .

EDIT: Menanggapi dua kritik (yang terlalu panjang untuk komentar):

Pertama, seperti yang dicatat orang lain, ya setiap basis data relasional yang mendukung pernyataan yang dipersiapkan dan parameter terikat tidak perlu mengkompilasi pernyataan yang disiapkan tanpa melihat nilai parameter terikat. Banyak database biasanya melakukan ini, tetapi juga memungkinkan bagi database untuk melihat nilai-nilai parameter terikat ketika mencari tahu rencana eksekusi. Ini bukan masalah, karena struktur pernyataan yang dipersiapkan dengan parameter terikat terpisah, membuatnya mudah bagi database untuk secara jelas membedakan pernyataan SQL (termasuk kata kunci SQL) dari data dalam parameter terikat (di mana tidak ada dalam parameter terikat akan menjadi diartikan sebagai kata kunci SQL). Ini tidak mungkin ketika membangun pernyataan SQL dari rangkaian string di mana variabel dan kata kunci SQL akan dicampur.

Kedua, sebagai jawaban lain menunjukkan, menggunakan parameter terikat saat memanggil pernyataan SQL pada satu titik dalam suatu program akan dengan aman mencegah injeksi SQL saat melakukan panggilan tingkat atas. Namun, jika Anda memiliki kerentanan injeksi SQL di tempat lain dalam aplikasi (mis., Dalam fungsi yang ditentukan pengguna, Anda menyimpan dan menjalankan dalam basis data yang Anda tulis secara tidak aman untuk membuat kueri SQL dengan penggabungan string).

Misalnya, jika dalam aplikasi Anda Anda menulis pseudo-code seperti:

sql_stmt = "SELECT create_new_user(?, ?)"
params = (email_str, hashed_pw_str)
db_conn.execute_with_params(sql_stmt, params)

Tidak ada injeksi SQL saat menjalankan pernyataan SQL ini di tingkat aplikasi. Namun jika fungsi database yang ditentukan pengguna ditulis secara tidak aman (menggunakan sintaks PL/pgSQL):

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES (' || email_str || ', ' || hashed_pw_str || ');'
     EXECUTE sql_str;
END;
$$
LANGUAGE plpgsql;

maka Anda akan rentan terhadap serangan injeksi SQL, karena mengeksekusi pernyataan SQL yang dibangun melalui penggabungan string yang menggabungkan pernyataan SQL dengan string yang berisi nilai-nilai variabel yang ditetapkan pengguna.

Yang mengatakan kecuali Anda mencoba menjadi tidak aman (membangun pernyataan SQL melalui penggabungan string), akan lebih alami untuk menulis yang didefinisikan pengguna dengan cara yang aman seperti:

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
BEGIN
     INSERT INTO users VALUES (email_str, hashed_pw_str);
END;
$$
LANGUAGE plpgsql;

Lebih lanjut, jika Anda benar-benar merasa perlu untuk menyusun pernyataan SQL dari sebuah string dalam fungsi yang ditentukan pengguna, Anda masih dapat memisahkan variabel data dari pernyataan SQL dengan cara yang sama seperti parameter yang tersimpan/tersimpan/parameter bahkan dalam fungsi yang ditentukan pengguna. Misalnya dalam PL/pgSQL :

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES($1, $2)'
     EXECUTE sql_str USING email_str, hashed_pw_str;
END;
$$
LANGUAGE plpgsql;

Jadi, menggunakan pernyataan yang disiapkan aman dari injeksi SQL, asalkan Anda tidak hanya melakukan hal-hal yang tidak aman di tempat lain (yang membangun pernyataan SQL dengan string concatenation).

54
dr jimbob

100% aman? Bahkan tidak dekat. Parameter terikat (disiapkan dengan pernyataan bijak atau lainnya) secara efektif dapat mencegah, 100%, satu kelas kerentanan injeksi SQL (dengan asumsi tidak ada bug db dan implementasi yang waras) ). Mereka sama sekali tidak mencegah kelas lain. Perhatikan bahwa PostgreSQL (pilihan saya) memiliki opsi untuk mengikat parameter ke pernyataan ad hoc yang menghemat perjalanan pulang pergi terkait pernyataan yang disiapkan jika Anda tidak memerlukan fitur tertentu dari ini.

Anda harus mengetahui bahwa banyak database yang besar dan kompleks adalah program itu sendiri. Kompleksitas dari program-program ini sedikit berbeda, dan injeksi SQL adalah sesuatu yang harus diperhatikan dalam rutinitas pemrograman di dalam. Rutinitas seperti itu termasuk pemicu, fungsi yang ditentukan pengguna, prosedur tersimpan, dan sejenisnya. Tidak selalu jelas bagaimana hal-hal ini berinteraksi dari tingkat aplikasi karena banyak dba baik menyediakan beberapa tingkat abstraksi antara tingkat akses aplikasi dan tingkat penyimpanan.

Dengan parameter terikat, pohon kueri diuraikan, lalu, setidaknya dalam PostgreSQL, data dilihat untuk merencanakan. Rencananya dieksekusi. Dengan pernyataan yang disiapkan, paket disimpan sehingga Anda dapat menjalankan kembali paket yang sama dengan data yang berbeda berulang-ulang (ini mungkin atau mungkin bukan yang Anda inginkan). Tetapi intinya adalah bahwa dengan parameter terikat, parameter tidak dapat menyuntikkan apa pun ke pohon parse. Jadi masalah injeksi SQL kelas ini dirawat dengan benar.

Tapi sekarang kita perlu mencatat siapa yang menulis apa ke tabel, jadi kita menambahkan pemicu dan fungsi yang ditentukan pengguna untuk merangkum logika pemicu ini. Ini menimbulkan masalah baru. Jika Anda memiliki SQL dinamis di dalamnya, maka Anda harus khawatir tentang injeksi SQL di sana. Tabel yang mereka tulis mungkin memiliki pemicu sendiri, dan sebagainya. Demikian pula panggilan fungsi mungkin memunculkan permintaan lain yang mungkin memanggil panggilan fungsi lain dan sebagainya. Masing-masing direncanakan secara terpisah dari pohon utama.

Artinya, jika saya menjalankan kueri dengan parameter terikat seperti foo'; drop user postgres; -- maka itu tidak dapat secara langsung melibatkan pohon kueri tingkat atas dan menyebabkannya menambahkan perintah lain untuk menjatuhkan pengguna postgres. Namun, jika kueri ini memanggil fungsi lain secara langsung atau tidak, ada kemungkinan bahwa di suatu tempat di telepon, suatu fungsi akan rentan dan pengguna postgres akan dijatuhkan. Parameter terikat menawarkan no perlindungan ke kueri sekunder. Kueri sekunder itu perlu memastikan bahwa mereka menggunakan parameter terikat juga sejauh mungkin, dan jika tidak, perlu menggunakan rutinitas kutipan yang tepat.

Lubang kelinci itu dalam.

BTW untuk pertanyaan tentang Stack Overflow di mana masalah ini jelas, lihat https://stackoverflow.com/questions/37878426/conditional-where-expression-in-dynamic-query/37878574#37878574

Juga versi yang lebih bermasalah (karena pembatasan pada pernyataan utilitas) di https://stackoverflow.com/questions/38016764/perform-create-index-in-plpgsql-doesnt-run/38021245#38021245

25
Chris Travers