Mengapa SQL Injection tidak terjadi pada kueri ini di dalam prosedur tersimpan?

18

Saya membuat prosedur tersimpan berikut ini:

ALTER PROCEDURE usp_actorBirthdays (@nameString nvarchar(100), @actorgender nvarchar(100))
AS
SELECT ActorDOB, ActorName FROM tblActor
WHERE ActorName LIKE '%' + @nameString + '%'
AND ActorGender = @actorgender

Sekarang, saya mencoba melakukan sesuatu seperti ini. Mungkin saya melakukan ini salah, tapi saya ingin memastikan bahwa prosedur seperti itu dapat mencegah SQL Injection:

EXEC usp_actorBirthdays 'Tom', 'Male; DROP TABLE tblActor'

Gambar di bawah ini menunjukkan SQL di atas dieksekusi dalam SSMS dan hasilnya ditampilkan dengan benar alih-alih kesalahan:

masukkan deskripsi gambar di sini

Btw, saya menambahkan bagian yang mengikuti tanda titik koma setelah permintaan selesai dieksekusi. Kemudian saya menjalankannya lagi, tetapi ketika saya memeriksa untuk melihat apakah tabel tblActor ada atau tidak, itu masih ada. Apakah saya melakukan sesuatu yang salah? Atau apakah ini benar-benar anti-injeksi? Saya kira apa yang saya coba tanyakan di sini juga adalah apakah prosedur tersimpan seperti ini aman? Terima kasih.

Ravi
sumber
Sudahkah Anda mencoba iniEXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'
MarmiK

Jawaban:

38

Kode ini berfungsi dengan baik karena:

  1. Parameter, dan
  2. Tidak melakukan Dynamic SQL

Agar SQL Injection berfungsi, Anda harus membuat string kueri (yang tidak Anda lakukan) dan tidak menerjemahkan apostrof tunggal ( ') ke dalam escaped-apostrophes ( '') (yang diloloskan melalui parameter input).

Dalam upaya Anda untuk memberikan nilai "kompromi", 'Male; DROP TABLE tblActor'string itu hanya itu, string biasa.

Sekarang, jika Anda melakukan sesuatu seperti:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = '
          + @InputParam;

EXEC(@SQL);

maka yang akan rentan terhadap SQL Injection karena itu permintaan tidak di saat ini, konteks pra-parsing; permintaan itu hanyalah string lain saat ini. Jadi nilai @InputParambisa '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;dan yang mungkin menimbulkan masalah karena permintaan itu akan diberikan, dan dieksekusi, seperti:

SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;

Ini adalah salah satu (dari beberapa) alasan utama untuk menggunakan Prosedur Tersimpan: secara inheren lebih aman (well, selama Anda tidak menghindari keamanan itu dengan membangun kueri seperti yang saya perlihatkan di atas tanpa memvalidasi nilai dari parameter apa pun yang digunakan). Meskipun jika Anda perlu membangun SQL Dinamis, cara yang lebih disukai adalah membuat parameter itu juga menggunakan sp_executesql:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = @SomeDate_tmp';

EXEC sp_executesql
  @SQL,
  N'SomeDate_tmp DATETIME',
  @SomeDate_tmp = @InputParam;

Dengan menggunakan pendekatan ini, seseorang yang mencoba '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;untuk DATETIMEmemasukkan parameter input akan mendapatkan kesalahan saat menjalankan Prosedur Tersimpan. Atau bahkan jika Prosedur Tersimpan diterima @InputParametersebagai NVARCHAR(100), itu harus dikonversi ke DATETIMEdalam untuk masuk ke sp_executesqlpanggilan itu. Dan bahkan jika parameter dalam SQL Dinamis adalah tipe string, masuk ke Prosedur Tersimpan di tempat pertama setiap tanda kutip otomatis akan lolos ke tanda kutip ganda.

Ada jenis serangan yang kurang dikenal di mana penyerang mencoba untuk mengisi bidang input dengan tanda kutip sedemikian sehingga string di dalam Prosedur Tersimpan yang akan digunakan untuk membangun SQL Dinamis tetapi yang dinyatakan terlalu kecil tidak dapat memuat semuanya dan mendorong tanda kutip akhir dan entah bagaimana berakhir dengan jumlah tanda kutip yang benar sehingga tidak lagi "melarikan diri" dalam string. Ini disebut Pemotongan SQL dan dibicarakan dalam artikel majalah MSDN berjudul "Serangan Pemotongan SQL Baru dan Cara Menghindarinya", oleh Bala Neerumalla, tetapi artikel itu tidak lagi online. Masalah yang mengandung artikel ini - edisi November, 2006 MSDN Magazine - hanya tersedia sebagai file Bantuan Windows (dalam .chmformat). Jika Anda mengunduhnya, itu mungkin tidak terbuka karena pengaturan keamanan default. Jika ini terjadi, klik kanan pada file MSDNMagazineNovember2006en-us.chm dan pilih "Properties". Di salah satu tab itu akan ada opsi untuk "Percayai jenis file ini" (atau sesuatu seperti itu) yang perlu diperiksa / diaktifkan. Klik tombol "OK" dan kemudian coba buka kembali file .chm .

Variasi lain dari serangan Truncation adalah, dengan asumsi variabel lokal digunakan untuk menyimpan nilai "aman" yang disediakan pengguna karena setiap tanda kutip ganda digandakan sehingga dapat diloloskan, untuk mengisi variabel lokal itu dan menempatkan tanda kutip tunggal pada akhirnya. Idenya di sini adalah bahwa jika variabel lokal tidak berukuran benar, tidak akan ada cukup ruang di akhir untuk penawaran tunggal kedua, biarkan variabel diakhiri dengan penawaran tunggal tunggal yang kemudian digabungkan dengan penawaran tunggal yang mengakhiri nilai literal dalam SQL Dinamis, mengubah kutip tunggal yang berakhir menjadi kutipan tunggal yang diloloskan, dan string literal dalam Dynamic SQL kemudian diakhiri dengan kutipan-tunggal berikutnya yang dimaksudkan untuk memulai string literal berikutnya. Sebagai contoh:

-- Parameters:
DECLARE @UserID      INT = 37,
        @NewPassword NVARCHAR(15) = N'Any Value ....''',
        @OldPassword NVARCHAR(15) = N';Injected SQL--';

-- Stored Proc:
DECLARE @SQL NVARCHAR(MAX),
        @NewPassword_fixed NVARCHAR(15) = REPLACE(@NewPassword, N'''', N''''''),
        @OldPassword_fixed NVARCHAR(15) = REPLACE(@OldPassword, N'''', N'''''');

SELECT @NewPassword AS [@NewPassword],
       REPLACE(@NewPassword, N'''', N'''''') AS [REPLACE output],
       @NewPassword_fixed AS [@NewPassword_fixed];
/*
@NewPassword          REPLACE output          @NewPassword_fixed
Any Value ....'       Any Value ....''        Any Value ....'
*/

SELECT @OldPassword AS [@OldPassword],
       REPLACE(@OldPassword, N'''', N'''''') AS [REPLACE output],
       @OldPassword_fixed AS [@OldPassword_fixed];
/*
@OldPassword          REPLACE output          @OldPassword_fixed
;Injected SQL--       ;Injected SQL--         ;Injected SQL--
*/

SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
           + @NewPassword_fixed + N''' WHERE [TableNameID] = '
           + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
           + @OldPassword_fixed + N''';';

SELECT @SQL AS [Injected];

Di sini, Dynamic SQL yang akan dieksekusi sekarang:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';

Dynamic SQL yang sama, dalam format yang lebih mudah dibaca, adalah:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';

Injected SQL--';

Memperbaiki ini mudah. Lakukan saja salah satu dari yang berikut:

  1. JANGAN MENGGUNAKAN DINAMIS SQL KECUALI TANPA KEBUTUHAN! (Saya mendaftar ini pertama karena itu benar-benar harus menjadi hal pertama yang harus dipertimbangkan).
  2. Ukuran yang benar variabel lokal (yaitu harus dua kali ukuran sebagai parameter input, kalau-kalau semua karakter yang lewat adalah tanda kutip tunggal.
  3. Jangan gunakan variabel lokal untuk menyimpan nilai "tetap"; cukup masukkan REPLACE()langsung ke dalam penciptaan Dynamic SQL:

    SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
               + REPLACE(@NewPassword, N'''', N'''''') + N''' WHERE [TableNameID] = '
               + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
               + REPLACE(@OldPassword, N'''', N'''''') + N''';';
    
    SELECT @SQL AS [No SQL Injection here];

    Dynamic SQL tidak lagi dikompromikan:

    UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';

Catatan tentang contoh Trunction di atas:

  1. Ya, ini adalah contoh yang sangat dibuat-buat. Tidak banyak yang bisa dilakukan hanya dalam 15 karakter untuk disuntikkan. Tentu, mungkin DELETE tableNamemenjadi destruktif, tetapi kecil kemungkinannya untuk menambahkan pengguna pintu belakang, atau mengubah kata sandi admin.
  2. Jenis serangan ini mungkin membutuhkan pengetahuan tentang kode, nama tabel, dll. Lebih sedikit kemungkinan dilakukan oleh orang asing / skrip-kiddie acak, tetapi saya memang bekerja di tempat yang diserang oleh mantan karyawan yang agak kesal yang mengetahui kerentanan. di satu halaman web tertentu yang tidak diketahui orang lain. Artinya, terkadang penyerang memiliki pengetahuan yang mendalam tentang sistem.
  3. Tentu, mengatur ulang kata sandi semua orang kemungkinan akan diselidiki, yang mungkin memberi tahu perusahaan bahwa ada serangan, tetapi mungkin masih menyediakan cukup waktu untuk menyuntikkan pengguna pintu belakang atau mungkin mendapatkan info sekunder untuk digunakan / dieksploitasi nanti.
  4. Sekalipun skenario ini sebagian besar bersifat akademis (artinya tidak mungkin terjadi di dunia nyata), itu tetap tidak mustahil.

Untuk informasi lebih rinci terkait dengan SQL Injection (mencakup berbagai RDBMS dan skenario), silakan lihat yang berikut dari Proyek Keamanan Aplikasi Web Terbuka (OWASP):
Pengujian untuk SQL Injection

Terkait Stack Overflow jawaban pada SQL Injection dan SQL Truncation:
Seberapa amankah T-SQL setelah Anda mengganti karakter 'escape?

Solomon Rutzky
sumber
2
Oh, terima kasih banyak, ini jawaban yang bagus. Saya mengerti sekarang. Saya benar-benar ingin melihat teknik yang Anda sebutkan di bagian akhir juga di mana penyerang mencoba untuk mengisi bidang input dengan tanda kutip, jika Anda dapat menemukannya. Terima kasih sebelumnya. Saya akan tetap membuka ini, jika Anda tidak menemukannya, saya akan memilih ini sebagai jawabannya.
Ravi
1
@Ravi Saya menemukan tautan tetapi tidak lagi masuk ke artikel karena semuanya sudah diarsipkan. Tetapi saya menambahkan beberapa info dan tautan yang bermanfaat dan saya masih berusaha menemukan artikel di dalam arsip tersebut.
Solomon Rutzky
1
Terima kasih srutzsky, saya akan membaca artikel OWASP dan tes untuk injeksi. Jika saya ingat dengan benar, 'mutillidae', aplikasi web yang rentan untuk pengujian keamanan, memiliki injeksi SQL yang saya lakukan di kampus dengan string 'OR 1 = 1' yang pada mutillidae mengakibatkan saya masuk ke aplikasi web sebagai admin I berpikir. Saat itulah saya pertama kali diperkenalkan dengan injeksi SQL.
Ravi
1
Saya tidak dapat melihat file .chm juga, tetapi terima kasih atas jawaban yang sempurna ini dan untuk semua tautan bermanfaat termasuk yang dari stackoverflow dan yang dari OWASP. Saya membacanya hari ini dan belajar banyak dari ini.
Ravi
2

Masalah sederhananya adalah Anda sama sekali tidak membingungkan data dengan perintah. Nilai untuk parameter tidak pernah diperlakukan sebagai bagian dari perintah, dan karenanya tidak pernah dieksekusi.

Saya membuat blog tentang ini di: http://blogs.lobsterpot.com.au/2015/02/10/sql-injection-the-golden-rule/

Rob Farley
sumber
Terima kasih Rob, saya punya bookmark yang ini untuk dibaca nanti.
Ravi