Mã này hoạt động đúng bởi vì nó là:
- Tham số hóa, và
- Không thực hiện bất kỳ SQL động nào
Để SQL Injection hoạt động, bạn phải xây dựng một chuỗi truy vấn (mà bạn không làm) và không dịch các dấu nháy đơn ( '
) thành các dấu nháy đơn ( ''
) được thoát qua các tham số đầu vào).
Trong nỗ lực của bạn để vượt qua một giá trị "bị xâm phạm", 'Male; DROP TABLE tblActor'
chuỗi chỉ là một chuỗi đơn giản.
Bây giờ, nếu bạn đang làm một cái gì đó dọc theo dòng:
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'SELECT fields FROM table WHERE field23 = '
+ @InputParam;
EXEC(@SQL);
sau đó sẽ dễ bị SQL Injection vì truy vấn đó không nằm trong bối cảnh hiện tại, được phân tích cú pháp trước; truy vấn đó chỉ là một chuỗi khác tại thời điểm này. Vì vậy, giá trị của @InputParam
có thể '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
và điều đó có thể gây ra vấn đề vì truy vấn đó sẽ được hiển thị và được thực thi, như:
SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
Đây là một (một vài) lý do chính để sử dụng Quy trình được lưu trữ: vốn đã an toàn hơn (miễn là bạn không phá vỡ sự bảo mật đó bằng cách xây dựng các truy vấn như tôi đã trình bày ở trên mà không xác thực các giá trị của bất kỳ tham số nào được sử dụng). Mặc dù nếu bạn cần xây dựng SQL động, cách ưa thích là sử dụng tham số đó bằng cách sử dụng 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;
Sử dụng phương pháp này, ai đó cố gắng để vượt qua trong '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
một DATETIME
tham số đầu vào sẽ nhận được một lỗi khi thực hiện các thủ tục lưu trữ. Hoặc thậm chí nếu các Stored Procedure chấp nhận @InputParameter
như NVARCHAR(100)
, nó sẽ phải chuyển đổi sang một DATETIME
để thông qua vào mà sp_executesql
gọi. Và ngay cả khi tham số trong SQL động là loại chuỗi, đi vào Quy trình được lưu trữ ở vị trí đầu tiên, bất kỳ dấu nháy đơn nào cũng sẽ tự động thoát khỏi dấu nháy đơn.
Có một kiểu tấn công ít được biết đến hơn, trong đó kẻ tấn công cố gắng lấp đầy trường đầu vào bằng dấu nháy đơn để một chuỗi bên trong Quy trình được lưu trữ sẽ được sử dụng để xây dựng SQL động nhưng được khai báo quá nhỏ không thể phù hợp với mọi thứ và đẩy ra dấu nháy đơn kết thúc và bằng cách nào đó kết thúc với số dấu nháy đơn chính xác để không còn bị "thoát" trong chuỗi. Điều này được gọi là Cắt ngắn SQL và đã được nói đến trong một bài viết trên tạp chí MSDN có tiêu đề "Tấn công cắt ngắn SQL mới và cách tránh chúng", bởi Bala Neerumalla, nhưng bài viết không còn trực tuyến nữa. Vấn đề có chứa bài viết này - Tạp chí MSDN phiên bản tháng 11 năm 2006 - chỉ có sẵn dưới dạng tệp Trợ giúp của Windows (trong .chmđịnh dạng). Nếu bạn tải xuống, nó có thể không mở do cài đặt bảo mật mặc định. Nếu điều này xảy ra, sau đó nhấp chuột phải vào tệp MSDNMagazineNovember2006en-us.chm và chọn "Thuộc tính". Trong một trong các tab đó, sẽ có một tùy chọn cho "Tin tưởng loại tệp này" (hoặc một cái gì đó tương tự) cần được kiểm tra / kích hoạt. Nhấp vào nút "OK" và sau đó thử mở lại tệp .chm .
Một biến thể khác của tấn công Cắt ngắn là, giả sử một biến cục bộ được sử dụng để lưu trữ giá trị do người dùng cung cấp "an toàn" vì nó có bất kỳ dấu ngoặc đơn nào được nhân đôi để thoát ra, để điền vào biến cục bộ đó và đặt dấu ngoặc đơn cuối cùng. Ý tưởng ở đây là nếu biến cục bộ không có kích thước phù hợp, cuối cùng sẽ không có đủ chỗ cho trích dẫn đơn thứ hai, hãy để biến kết thúc bằng một trích dẫn đơn sau đó kết hợp với trích dẫn đơn kết thúc giá trị bằng chữ trong SQL động, biến đoạn kết thúc trích dẫn đó thành một trích dẫn thoát được nhúng và chuỗi ký tự trong SQL động sau đó kết thúc bằng trích dẫn đơn tiếp theo được dự định để bắt đầu chuỗi ký tự tiếp theo. Ví dụ:
-- 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];
Ở đây, SQL động sẽ được thực thi ngay bây giờ:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
Cùng một SQL động, ở định dạng dễ đọc hơn, là:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';
Injected SQL--';
Sửa lỗi này là dễ dàng. Chỉ cần làm một trong những điều sau đây:
- KHÔNG SỬ DỤNG SQL NĂNG ĐỘNG KHÔNG GIỚI HẠN ! (Tôi liệt kê cái này trước vì nó thực sự nên là thứ đầu tiên cần xem xét).
- Kích thước chính xác biến cục bộ (nghĩa là phải gấp đôi kích thước làm tham số đầu vào, chỉ trong trường hợp tất cả các ký tự được truyền vào là dấu ngoặc đơn.
Không sử dụng biến cục bộ để lưu trữ giá trị "cố định"; chỉ cần đặt REPLACE()
trực tiếp vào việc tạo SQL động:
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 không còn bị xâm phạm:
UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
Lưu ý về ví dụ Tractor ở trên:
- Vâng, đây là một ví dụ rất giả tạo. Không có nhiều thứ người ta có thể làm chỉ trong 15 ký tự. Chắc chắn, có thể
DELETE tableName
là phá hoại, nhưng ít có khả năng thêm người dùng cửa sau hoặc thay đổi mật khẩu quản trị viên.
- Kiểu tấn công này có thể đòi hỏi kiến thức về mã, tên bảng, v.v ... Ít có khả năng được thực hiện bởi người lạ ngẫu nhiên / script-kiddie, nhưng tôi đã làm việc tại một nơi bị tấn công bởi một nhân viên cũ khá khó chịu, người biết về lỗ hổng trong một trang web cụ thể mà không ai khác biết. Có nghĩa là, đôi khi những kẻ tấn công có kiến thức sâu sắc về hệ thống.
- Chắc chắn, việc thiết lập lại mật khẩu của mọi người có khả năng sẽ được điều tra, điều này có thể khiến công ty hiểu rằng có một cuộc tấn công đang xảy ra, nhưng nó vẫn có thể cung cấp đủ thời gian để tiêm cho người dùng cửa sau hoặc có thể lấy một số thông tin thứ cấp để sử dụng / khai thác sau này.
- Ngay cả khi kịch bản này chủ yếu là học thuật (nghĩa là không có khả năng xảy ra trong thế giới thực), nó vẫn không phải là không thể.
Để biết thêm thông tin chi tiết liên quan đến SQL Injection (bao gồm các kịch bản và kịch bản khác nhau của RDBMS), vui lòng xem phần sau đây từ Dự án bảo mật ứng dụng web mở (OWASP):
Kiểm tra SQL SQL
Câu trả lời tràn ngăn xếp liên quan về SQL Injection và SQL Truncation:
T-SQL an toàn đến mức nào sau khi bạn thay thế ký tự thoát?
EXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'