Injeksi SQL

Berlaku untuk: SQL Server (semua versi yang didukung) Azure SQL Database Azure SQL Managed Instance Azure Synapse Analytics Analytics Platform System (PDW)

Injeksi SQL adalah serangan di mana kode berbahaya dimasukkan ke dalam string yang kemudian diteruskan ke instans SQL Server untuk penguraian dan eksekusi. Prosedur apa pun yang membangun pernyataan SQL harus ditinjau untuk kerentanan injeksi karena SQL Server akan menjalankan semua kueri yang valid secara sintaksis yang diterimanya. Bahkan data berparameter dapat dimanipulasi oleh penyerang yang terampil dan ditentukan.

Cara Kerja Injeksi SQL

Bentuk utama injeksi SQL terdiri dari penyisipan kode langsung ke dalam variabel input pengguna yang digabungkan dengan perintah SQL dan dijalankan. Serangan yang kurang langsung menyuntikkan kode berbahaya ke dalam string yang ditujukan untuk penyimpanan dalam tabel atau sebagai metadata. Ketika string yang disimpan kemudian digabungkan ke dalam perintah SQL dinamis, kode berbahaya dijalankan.

Proses injeksi bekerja dengan menghentikan string teks sebelum waktunya dan menambahkan perintah baru. Karena perintah yang disisipkan mungkin memiliki string tambahan yang ditambahkan ke dalamnya sebelum dijalankan, malefactor mengakhiri string yang disuntikkan dengan tanda komentar "--". Teks berikutnya diabaikan pada waktu eksekusi.

Skrip berikut menunjukkan injeksi SQL sederhana. Skrip membuat kueri SQL dengan menggabungkan string yang dikodekan secara permanen bersama dengan string yang dimasukkan oleh pengguna:

var Shipcity;  
ShipCity = Request.form ("ShipCity");  
var sql = "select * from OrdersTable where ShipCity = '" + ShipCity + "'";  

Pengguna diminta untuk memasukkan nama kota. Jika dia memasukkan Redmond, kueri yang dirakit oleh skrip terlihat mirip dengan yang berikut ini:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond'  

Namun, asumsikan bahwa pengguna memasukkan hal berikut:

Redmond'; drop table OrdersTable--  

Dalam hal ini, kueri berikut dirakit oleh skrip:

SELECT * FROM OrdersTable WHERE ShipCity = 'Redmond';drop table OrdersTable--'  

Titik koma (;) menunjukkan akhir dari satu kueri dan awal kueri lainnya. Tanda hubung ganda (--) menunjukkan bahwa sisa baris saat ini adalah komentar dan harus diabaikan. Jika kode yang dimodifikasi secara sintaksis benar, kode tersebut akan dijalankan oleh server. Ketika SQL Server memproses pernyataan ini, SQL Server akan terlebih dahulu memilih semua rekaman di OrdersTable mana ShipCity adalah Redmond. Kemudian, SQL Server akan menjatuhkan OrdersTable.

Selama kode SQL yang disuntikkan benar secara sintaksis, perubahan tidak dapat dideteksi secara terprogram. Oleh karena itu, Anda harus memvalidasi semua input pengguna dan meninjau kode dengan hati-hati yang menjalankan perintah SQL yang dibangun di server yang Anda gunakan. Praktik terbaik pengodean dijelaskan di bagian berikut dalam topik ini.

Validasi Semua Input

Selalu validasi input pengguna dengan jenis pengujian, panjang, format, dan rentang. Saat Anda menerapkan tindakan pencegahan terhadap input berbahaya, pertimbangkan skenario arsitektur dan penyebaran aplikasi Anda. Ingatlah bahwa program yang dirancang untuk berjalan di lingkungan yang aman dapat disalin ke lingkungan yang tidak aman. Saran berikut harus dianggap sebagai praktik terbaik:

  • Jangan buat asumsi tentang ukuran, jenis, atau konten data yang diterima oleh aplikasi Anda. Misalnya, Anda harus membuat evaluasi berikut:

    • Bagaimana aplikasi Anda akan bertingkah jika pengguna yang salah atau berbahaya memasukkan file MPEG 10 megabyte di mana aplikasi Anda mengharapkan kode pos?

    • Bagaimana aplikasi Anda akan bereaksi jika DROP TABLE pernyataan disematkan di bidang teks?

  • Uji ukuran dan jenis data input dan terpaksa batas yang sesuai. Ini dapat membantu mencegah overrun buffer yang disengaja.

  • Uji konten variabel string dan terima hanya nilai yang diharapkan. Tolak entri yang berisi data biner, urutan escape, dan karakter komentar. Ini dapat membantu mencegah injeksi skrip dan dapat melindungi dari beberapa eksploitasi overrun buffer.

  • Saat Anda bekerja dengan dokumen XML, validasi semua data terhadap skemanya saat dimasukkan.

  • Jangan pernah membangun pernyataan Transact-SQL langsung dari input pengguna.

  • Gunakan prosedur tersimpan untuk memvalidasi input pengguna.

  • Di lingkungan multitiered, semua data harus divalidasi sebelum masuk ke zona tepercaya. Data yang tidak lulus proses validasi harus ditolak dan kesalahan harus dikembalikan ke tingkat sebelumnya.

  • Menerapkan beberapa lapisan validasi. Tindakan pencegahan yang Anda lakukan terhadap pengguna yang berbahaya mungkin tidak efektif terhadap penyerang yang ditentukan. Praktik yang lebih baik adalah memvalidasi input di antarmuka pengguna dan di semua titik berikutnya di mana melewati batas kepercayaan.
    Misalnya, validasi data dalam aplikasi sisi klien dapat mencegah injeksi skrip sederhana. Namun, jika tingkat berikutnya mengasumsikan bahwa inputnya telah divalidasi, setiap pengguna berbahaya yang dapat melewati klien dapat memiliki akses tidak terbatas ke sistem.

  • Jangan pernah menggabungkan input pengguna yang tidak divalidasi. Perangkaian string adalah titik entri utama untuk injeksi skrip.

  • Jangan terima string berikut dalam bidang tempat nama file dapat dibangun: AUX, CLOCK$, COM1 hingga COM8, CON, CONFIG$, LPT1 hingga LPT8, NUL, dan PRN.

Jika bisa, tolak input yang berisi karakter berikut.

Karakter input Arti dalam Transact-SQL
; Pemisah kueri.
' Pemisah string data karakter.
-- Pemisah komentar satu baris. Teks mengikuti -- hingga akhir baris tersebut tidak dievaluasi oleh server.
/* ... */ Pemisah komentar. Teks antara /* dan */ tidak dievaluasi oleh server.
xp_ Digunakan pada awal nama prosedur tersimpan yang diperluas katalog, seperti xp_cmdshell.

Menggunakan Parameter SQL Type-Safe

Koleksi Parameter di SQL Server menyediakan pemeriksaan jenis dan validasi panjang. Jika Anda menggunakan koleksi Parameter , input diperlakukan sebagai nilai harfiah alih-alih sebagai kode yang dapat dieksekusi. Manfaat tambahan menggunakan koleksi Parameter adalah Anda dapat memberlakukan pemeriksaan jenis dan panjang. Nilai di luar rentang akan memicu pengecualian. Fragmen kode berikut menunjukkan penggunaan koleksi Parameter :

SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn);  
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;  
SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id",  
     SqlDbType.VarChar, 11);  
parm.Value = Login.Text;  

Dalam contoh ini, @au_id parameter diperlakukan sebagai nilai harfiah alih-alih sebagai kode yang dapat dieksekusi. Nilai ini diperiksa untuk jenis dan panjang. Jika nilai tidak mematuhi jenis dan batasan @au_id panjang yang ditentukan, pengecualian akan dilemparkan.

Gunakan Input Berparameter dengan Prosedur Tersimpan

Prosedur tersimpan mungkin rentan terhadap injeksi SQL jika menggunakan input yang tidak difilter. Misalnya, kode berikut rentan:

SqlDataAdapter myCommand =   
new SqlDataAdapter("LoginStoredProcedure '" +   
                               Login.Text + "'", conn);  

Jika Anda menggunakan prosedur tersimpan, Anda harus menggunakan parameter sebagai inputnya.

Menggunakan Koleksi Parameter dengan SQL Dinamis

Jika Anda tidak dapat menggunakan prosedur tersimpan, Anda masih dapat menggunakan parameter, seperti yang ditunjukkan dalam contoh kode berikut.

SqlDataAdapter myCommand = new SqlDataAdapter(  
"SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn);  
SQLParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id",   
                        SqlDbType.VarChar, 11);  
Parm.Value = Login.Text;  

Input Pemfilteran

Pemfilteran input juga dapat membantu dalam melindungi dari injeksi SQL dengan menghapus karakter escape. Namun, karena banyaknya karakter yang dapat menimbulkan masalah, ini bukan pertahanan yang dapat diandalkan. Contoh berikut mencari pemisah string karakter.

private string SafeSqlLiteral(string inputSQL)  
{  
  return inputSQL.Replace("'", "''");  
}  

Klausul LIKE

Perhatikan bahwa jika Anda menggunakan LIKE klausul , karakter kartubebas masih harus diloloskan:

s = s.Replace("[", "[[]");  
s = s.Replace("%", "[%]");  
s = s.Replace("_", "[_]");  

Meninjau Kode untuk Injeksi SQL

Anda harus meninjau semua kode yang memanggil EXECUTE, , EXECatau sp_executesql. Anda dapat menggunakan kueri yang mirip dengan yang berikut ini untuk membantu Anda mengidentifikasi prosedur yang berisi pernyataan ini. Kueri ini memeriksa 1, 2, 3, atau 4 spasi setelah kata EXECUTE atau EXEC.

SELECT object_Name(id) FROM syscomments  
WHERE UPPER(text) LIKE '%EXECUTE (%'  
OR UPPER(text) LIKE '%EXECUTE  (%'  
OR UPPER(text) LIKE '%EXECUTE   (%'  
OR UPPER(text) LIKE '%EXECUTE    (%'  
OR UPPER(text) LIKE '%EXEC (%'  
OR UPPER(text) LIKE '%EXEC  (%'  
OR UPPER(text) LIKE '%EXEC   (%'  
OR UPPER(text) LIKE '%EXEC    (%'  
OR UPPER(text) LIKE '%SP_EXECUTESQL%';  

Parameter Pembungkusan dengan QUOTENAME() dan REPLACE()

Dalam setiap prosedur tersimpan yang dipilih, verifikasi bahwa semua variabel yang digunakan dalam Transact-SQL dinamis ditangani dengan benar. Data yang berasal dari parameter input prosedur tersimpan atau yang dibaca dari tabel harus dibungkus dalam QUOTENAME() atau REPLACE(). Ingatlah bahwa nilai @variable yang diteruskan ke QUOTENAME() adalah sysname, dan memiliki panjang maksimum 128 karakter.

@variable Pembungkus yang direkomendasikan
Nama yang dapat diamankan QUOTENAME(@variable)
String karakter ≤128 QUOTENAME(@variable, '''')
> String 128 karakter REPLACE(@variable,'''', '''''')

Saat Anda menggunakan teknik ini, pernyataan SET dapat direvisi sebagai berikut:

-- Before:  
SET @temp = N'SELECT * FROM authors WHERE au_lname ='''   
 + @au_lname + N'''';  
  
-- After:  
SET @temp = N'SELECT * FROM authors WHERE au_lname = '''   
 + REPLACE(@au_lname,'''','''''') + N'''';  

Injeksi Diaktifkan oleh Pemotongan Data

Setiap Transact-SQL dinamis yang ditetapkan ke variabel akan dipotong jika lebih besar dari buffer yang dialokasikan untuk variabel tersebut. Penyerang yang mampu memaksa pemotongan pernyataan dengan meneruskan string yang tidak terduga panjang ke prosedur tersimpan dapat memanipulasi hasilnya. Misalnya, prosedur tersimpan yang dibuat oleh skrip berikut rentan terhadap injeksi yang diaktifkan oleh pemotongan.

CREATE PROCEDURE sp_MySetPassword  
@loginname sysname,  
@old sysname,  
@new sysname  
AS  
-- Declare variable.  
-- Note that the buffer here is only 200 characters long.   
DECLARE @command varchar(200)  
-- Construct the dynamic Transact-SQL.  
-- In the following statement, we need a total of 154 characters   
-- to set the password of 'sa'.   
-- 26 for UPDATE statement, 16 for WHERE clause, 4 for 'sa', and 2 for  
-- quotation marks surrounded by QUOTENAME(@loginname):  
-- 200 - 26 - 16 - 4 - 2 = 154.  
-- But because @new is declared as a sysname, this variable can only hold  
-- 128 characters.   
-- We can overcome this by passing some single quotation marks in @new.  
SET @command= 'update Users set password='   
    + QUOTENAME(@new, '''') + ' where username='   
    + QUOTENAME(@loginname, '''') + ' AND password = '   
    + QUOTENAME(@old, '''')  
  
-- Execute the command.  
EXEC (@command)  
GO  

Dengan meneruskan 154 karakter ke dalam buffer 128 karakter, penyerang dapat mengatur kata sandi baru untuk sa tanpa mengetahui kata sandi lama.

EXEC sp_MySetPassword 'sa', 'dummy',   
'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012'''''''''''''''''''''''''''''''''''''''''''''''''''   

Untuk alasan ini, Anda harus menggunakan buffer besar untuk variabel perintah atau langsung menjalankan Transact-SQL dinamis di dalam EXECUTE pernyataan .

Pemotongan Saat QUOTENAME(@variable, '''') dan REPLACE() Digunakan

String yang dikembalikan oleh QUOTENAME() dan REPLACE() akan dipotong secara diam-diam jika melebihi spasi yang dialokasikan. Prosedur tersimpan yang dibuat dalam contoh berikut menunjukkan apa yang bisa terjadi.

CREATE PROCEDURE sp_MySetPassword  
    @loginname sysname,  
    @old sysname,  
    @new sysname  
AS  
  
-- Declare variables.  
    DECLARE @login sysname  
    DECLARE @newpassword sysname  
    DECLARE @oldpassword sysname  
    DECLARE @command varchar(2000)  
  
-- In the following statements, the data stored in temp variables  
-- will be truncated because the buffer size of @login, @oldpassword,  
-- and @newpassword is only 128 characters, but QUOTENAME() can return  
-- up to 258 characters.  
    SET @login = QUOTENAME(@loginname, '''')  
    SET @oldpassword = QUOTENAME(@old, '''')  
    SET @newpassword = QUOTENAME(@new, '''')  
  
-- Construct the dynamic Transact-SQL.  
-- If @new contains 128 characters, then @newpassword will be '123... n  
-- where n is the 127th character.   
-- Because the string returned by QUOTENAME() will be truncated,   
-- it can be made to look like the following statement:  
-- UPDATE Users SET password ='1234. . .[127] WHERE username=' -- other stuff here  
    SET @command = 'UPDATE Users set password = ' + @newpassword   
     + ' where username =' + @login + ' AND password = ' + @oldpassword;  
  
-- Execute the command.  
EXEC (@command);  
GO  

Oleh karena itu, pernyataan berikut akan mengatur kata sandi semua pengguna ke nilai yang diteruskan dalam kode sebelumnya

EXEC sp_MyProc '--', 'dummy', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'  

Anda dapat memaksa pemotongan string dengan melebihi ruang buffer yang dialokasikan saat Anda menggunakan REPLACE(). Prosedur tersimpan yang dibuat dalam contoh berikut menunjukkan apa yang bisa terjadi.

CREATE PROCEDURE sp_MySetPassword  
    @loginname sysname,  
    @old sysname,  
    @new sysname  
AS  
  
-- Declare variables.  
    DECLARE @login sysname  
    DECLARE @newpassword sysname  
    DECLARE @oldpassword sysname  
    DECLARE @command varchar(2000)  
  
-- In the following statements, data will be truncated because   
-- the buffers allocated for @login, @oldpassword and @newpassword   
-- can hold only 128 characters, but QUOTENAME() can return   
-- up to 258 characters.   
    SET @login = REPLACE(@loginname, '''', '''''')  
    SET @oldpassword = REPLACE(@old, '''', '''''')  
    SET @newpassword = REPLACE(@new, '''', '''''')  
  
-- Construct the dynamic Transact-SQL.  
-- If @new contains 128 characters, @newpassword will be '123...n   
-- where n is the 127th character.   
-- Because the string returned by QUOTENAME() will be truncated, it  
-- can be made to look like the following statement:  
-- UPDATE Users SET password='1234...[127] WHERE username=' -- other stuff here   
    SET @command= 'update Users set password = ''' + @newpassword + ''' where username='''   
     + @login + ''' AND password = ''' + @oldpassword + '''';  
  
-- Execute the command.  
EXEC (@command);  
GO  

Seperti halnya QUOTENAME(), pemotongan string oleh REPLACE() dapat dihindari dengan mendeklarasikan variabel sementara yang cukup besar untuk semua kasus. Jika memungkinkan, Anda harus memanggil QUOTENAME() atau REPLACE() langsung di dalam Transact-SQL dinamis. Jika tidak, Anda dapat menghitung ukuran buffer yang diperlukan sebagai berikut. Untuk @outbuffer = QUOTENAME(@input), ukuran @outbuffer harus 2*(len(@input)+1). Saat Anda menggunakan REPLACE() dan menggandakan tanda kutip, seperti dalam contoh sebelumnya, buffer 2*len(@input) sudah cukup.

Perhitungan berikut mencakup semua kasus:

WHILE LEN(@find_string) > 0, required buffer size =  
ROUND(LEN(@input)/LEN(@find_string),0) * LEN(@new_string)   
 + (LEN(@input) % LEN(@find_string))  

Pemotongan Ketika QUOTENAME(@variable, ']') Digunakan

Pemotongan dapat terjadi ketika nama SQL Server dapat diamankan diteruskan ke pernyataan yang menggunakan formulir QUOTENAME(@variable, ']'). Contoh berikut menunjukkan ini.

CREATE PROCEDURE sp_MyProc  
    @schemaname sysname,  
    @tablename sysname,  
AS  
  
-- Declare a variable as sysname. The variable will be 128 characters.  
-- But @objectname actually must allow for 2*258+1 characters.   
DECLARE @objectname sysname  
SET @objectname = QUOTENAME(@schemaname)+'.'+ QUOTENAME(@tablename)   
-- Do some operations.  
GO  

Ketika Anda menggabungkan nilai jenis sysname, Anda harus menggunakan variabel sementara yang cukup besar untuk menahan maksimum 128 karakter per nilai. Jika memungkinkan, panggil QUOTENAME() langsung di dalam Transact-SQL dinamis. Jika tidak, Anda dapat menghitung ukuran buffer yang diperlukan seperti yang dijelaskan di bagian sebelumnya.

Lihat juga

EXECUTE (Transact-SQL)
REPLACE (Transact-SQL)
QUOTENAME (Transact-SQL)
sp_executesql (T-SQL)
Mengamankan SQL Server