我一直在将不安全的动态查询转换为参数化查询。我已经找到了利用人们为净化输入而做出的大多数较弱尝试的方法,但我还没有弄清楚如何实际利用本质上的查询
DECLARE @input VARCHAR(100) = ''';SELECT ''INJECTED''--'
DECLARE @SQL NVARCHAR(100) = 'SELECT ''example'' WHERE ''1'' = ''' + REPLACE(@input, '''', '') + ''''
EXEC sp_executesql @SQL
或
DECLARE @input VARCHAR(100) = ''';SELECT ''INJECTED''--'
DECLARE @SQL NVARCHAR(100) = 'SELECT ''example'' WHERE ''1'' = ''' + REPLACE(@input, '''', '''''') + ''''
EXEC sp_executesql @SQL
我见过有人在线中断转义(在 MySQL 中,但从未在 TSQL 中),但我一直无法找到任何方法来中断剥离单引号。
我也计划修复上述实例,但我不知道如何证明它们不安全。
如何利用上述查询?
你可以而且应该仍然参数化你的动态sql以使其真正安全。您的示例查询可能是这样的。
declare @z int = 10
EXEC sp_executesql 'SELECT x FROM y WHERE MyColumn = @z', N'@z int', @z
现在我们已经在动态 sql 中创建了一个参数化查询并将参数传递给执行。这不容易受到 SQL 注入攻击。
--编辑--
既然您想要回答您的原始查询是否容易受到 SQL 注入攻击的问题,我将证明它实际上是相当开放的。当涉及到 SQL 注入时,您只处理单引号,这确实只是冰山一角。您是否考虑过如果传递字符串的二进制表示而不是字符串会发生什么?
假设您的查询正在接收 nvarchar(max) 作为参数。在我的示例中,我将此称为@BadCode。现在,在我的示例中,我不会对您的系统造成任何损害,但在野外,这个二进制文件实际上可以是任何东西。
这是一个简单的存储过程,其模式与您演示的模式非常接近。
create procedure InjectionTest
(
@BadCode nvarchar(max)
) as
set @BadCode = REPLACE(@BadCode, '''', '')
EXEC sp_executesql @BadCode
GO
现在从前端我将传入值 0x730065006C0065006300740020002A002000660072006F006D0020007300790073002E0064006100740061006200610073006500 7300。再次强调,这是无害的二进制文件。如果您想查看,只需将其放入 Convert(varchar(max),...
下面是调用上述过程的示例。传入的二进制字符串是用户将传入的内容。请注意,其中没有单引号。
declare @Test nvarchar(max) = 0x730065006C0065006300740020002A002000660072006F006D0020007300790073002E00640061007400610062006100730065007300
exec InjectionTest @Test
还有很多更长的解释和更多的技巧,但这演示了如何轻松破解它的基础知识。
正如其他人提到的,尝试清理输入是最令人担忧的选项。最好的选择是永远不要让输入成为可执行代码......
这是参数中 VARCHAR(MAX) 列表的安全模式。它确实需要 STRING_SPLIT 或等效函数(SQL Server 2016 及更高版本。不确定其他 SQL 风格)
CREATE PROCEDURE dbo.SelectSomeListOfIDs (
@AnyDelimitedIDList VARCHAR(MAX)
)
BEGIN
--The idea here is to make the list creation flexible and non-brittle for non-technical users and external calls. Overkill for most scenarios. Depending on the expected data, some delimiters should definitely be taken out.
REPLACE @AnyDelimitedIDList = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
@AnyDelimitedIDList
,CHAR(09),'|') --Tab
,CHAR(10),'|') --LF
,CHAR(13),'|') --CR
,CHAR(44),'|') --Comma
,CHAR(59),'|') --SemiColon
,CHAR(58),'|') --Colon
,CHAR(46),'|') --dot
,CHAR(34),'|') --DoubleQuote
,CHAR(39),'|') --SingleQuote
,CHAR(32),'|') --Space
--this enables the input to never become executable code
DROP TABLE IF EXISTS #IDList
SELECT ID = x.value
INTO #IDList
FROM STRING_SPLIT(@AnyDelimitedIDList,'|') x
WHERE ISNULL(x.value,'') <> ''
--Obviates the need for Dynamic SQL
SELECT ID
FROM dbo.MyTable x
JOIN #IDList list
ON x.ID = List.ID --safe from sql injection
--Dynamic SQL is no longer needed for the sake of the ID List, but if Dyn is still needed/desired, the list can still be simplified and made safe:
DECLARE @dummyScalar DATETIME = GETDATE()
EXEC ('
SELECT ID
FROM dbo.MyTable x
JOIN #IDList list --safe from sql injection, and a bit simpler
ON x.ID = List.ID
WHERE x.date > '''+@dummyScalar+'''
')
END