参数化语句可以停止所有SQL注入吗?


80

如果是,为什么还有那么多成功的SQL注入?仅仅因为某些开发人员太笨了而无法使用参数化语句?


6
这是一个很好的问题,答案绝对糟糕(就我评论的时间而言)
Ibu

我希望至少拥有15k信誉或经验的人可以为这个问题添加宝贵的意见。
伊布

4
有关此主题的更多信息,请参见Bill Karwin的 Sql注入神话和谬论 谈话幻灯片。他解释了什么是SQL注入,转义通常还不够好,以及如何损害存储过程和参数化语句。
麦克,

2
另请参阅Bill Karwin对类似问题回答:什么是SQL注入?
麦克,

Answers:


66

我在评论中张贴的对该问题的链接很好地说明了这个问题。下面总结了我对问题仍然存在的原因的看法:

  1. 刚开始的人可能不了解SQL注入。

  2. 有些人知道SQL注入,但认为转义是(唯一的)解决方案。如果您对Google进行快速搜索php mysql query,则显示的第一页是该mysql_query页面,该页面上有一个示例,该示例显示了将转义的用户输入插入到查询中。没有提到(至少我看不到)使用准备好的语句。就像其他人所说的那样,有太多使用参数插值的教程,因此使用频率仍然不足为奇。

  3. 缺乏对参数化语句如何工作的理解。有人认为这只是逃避价值的一种幻想手段。

  4. 其他人知道参数化的语句,但不要使用它们,因为他们听说它们太慢了。我怀疑许多人听到过如此缓慢的参数化陈述,但实际上并没有对他们自己进行任何测试。正如比尔·卡文(Bill Karwin)在演讲中所指出的那样,在考虑使用准备好的陈述时,应该很少将绩效差异作为因素。一次准备,多次执行的好处经常被忘记,安全性和代码可维护性方面的改善也常常被人们遗忘。

  5. 有些在各处都使用参数化的语句,但会插值未检查的值,例如表和列名称,关键字和条件运算符。动态搜索(例如,允许用户指定许多不同搜索字段,比较条件和排序顺序的搜索)就是其中的主要示例。

  6. 使用ORM时有错误的安全感。ORM仍然允许对SQL语句部分进行插值-参见5。

  7. 编程是一个大而复杂的主题,数据库管理是一个大而复杂的主题,安全性是一个大而复杂的主题。开发安全的数据库应用程序并非易事-即使是经验丰富的开发人员也可能会陷入困境。

  8. 关于stackoverflow的许多答案都无济于事。当人们编写使用动态SQL和参数插值的问题时,通常缺少建议使用参数化语句的答案。在某些情况下,我曾有人反对我使用准备好的语句的建议-通常是因为人们认为性能开销不可接受。我严重怀疑那些问大多数这些问题的人所处的位置,即准备参数化语句所花费的额外几毫秒会对其应用造成灾难性的影响。


65

当文章谈论停止SQL攻击的参数化查询时,他们并没有真正解释原因,通常是“确实如此,所以不要问为什么”的情况-可能是因为他们不了解自己。可以肯定的一个坏教育者的信号是,它不能承认他们不知道什么。但是我离题了。当我说我发现混淆时完全可以理解的很简单。想象一下动态SQL查询

sqlQuery='SELECT * FROM custTable WHERE User=' + Username + ' AND Pass=' + password

因此,简单的sql注入只是将Username输入为'OR 1 = 1--这将有效地进行sql查询:

sqlQuery='SELECT * FROM custTable WHERE User='' OR 1=1-- ' AND PASS=' + password

这表示选择所有用户名为空('')或1 = 1(这是布尔值,等于true)的客户。然后使用-注释掉其余的查询。因此,这只会打印出所有客户表,或执行您要执行的任何操作,如果登录,它将以第一个用户的权限登录,该用户通常可以是管理员。

现在,参数化查询的方式有所不同,例如:

sqlQuery='SELECT * FROM custTable WHERE User=? AND Pass=?'

parameters.add("User", username)
parameters.add("Pass", password)

其中用户名和密码是指向关联的输入的用户名和密码的变量

现在,您可能在想,这根本不会改变任何东西。当然,您仍然可以将诸如Nobody OR 1 = 1'-之类的内容输入用户名字段,从而有效地进行查询:

sqlQuery='SELECT * FROM custTable WHERE User=Nobody OR 1=1'-- AND Pass=?'

这似乎是一个有效的论点。但是,你会错的。

参数化查询的工作方式是将sqlQuery作为查询发送,并且数据库确切知道此查询将执行的操作,然后才将用户名和密码仅作为值插入。这意味着它们无法影响查询,因为数据库已经知道查询将执行的操作。因此,在这种情况下,它将查找用户名“ Nobody OR 1 = 1'-”和一个空密码,该密码应该为false。

但是,这不是一个完整的解决方案,并且仍然需要完成输入验证,因为这不会影响其他问题,例如XSS攻击,因为您仍然可以将javascript放入数据库中。然后,如果将其读出到页面上,则将根据任何输出验证将其显示为普通javascript。因此,实际上最好的办法仍然是使用输入验证,但是使用参数化查询或存储过程来停止任何SQL攻击。


1
这为我一直在寻找的东西增加了很多,但是您能否解释一下有关“输入验证”将要做什么?您还提到了查询可能还会发生其他攻击,例如XSS,但是您能解释一下这种情况如何发生吗?因此,从本质上讲,我们将如何充分防御SQL注入,还是在寻找所有类型的注入?谢谢。
XaolingBao

3
@JosipIvic:鉴于有多少人问过参数化语句的工作原理,看到如此之少(如果有的话)真的像您所做的那样分解答案,真是令人震惊。感谢您使用一个非常直观的示例编写了如此清晰的说明。
daOnlyBG

辉煌。一个例子可以说出一千个单词!
德雷奈

10

好问题。答案比确定性更随机,我将尝试通过一个小例子来解释我的观点。

网上有很多参考文献建议我们在查询中使用参数或对参数使用存储过程,以避免使用SQL Injection(SQLi)。我将向您展示存储过程(例如)并不是SQLi的魔术棒。责任仍然由程序员承担。

考虑以下SQL Server存储过程,该存储过程将从表'Users'中获取用户行:

create procedure getUser
 @name varchar(20)
,@pass varchar(20)
as
declare @sql as nvarchar(512)
set @sql = 'select usrID, usrUName, usrFullName, usrRoleID '+
           'from Users '+
           'where usrUName = '''+@name+''' and usrPass = '''+@pass+''''
execute(@sql)

您可以通过传递用户名和密码作为参数来获得结果。假设密码为自由文本形式(仅出于简化本示例的目的),则正常调用将为:

DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)

EXECUTE @RC = [dbo].[getUser] 
   @name = 'admin'
  ,@pass = '!@Th1siSTheP@ssw0rd!!'
GO

但是在这里,程序员在存储过程内部使用了一种不好的编程技术,因此攻击者可以执行以下操作:

DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)

EXECUTE @RC = [TestDB].[dbo].[getUser] 
   @name = 'admin'
  ,@pass = 'any'' OR 1=1 --'
GO

上面的参数将作为参数传递给存储过程,最终将执行的SQL命令为:

select usrID, usrUName, usrFullName, usrRoleID 
from Users 
where usrUName = 'admin' and usrPass = 'any' OR 1=1 --'

..这将使用户返回所有行

这里的问题是,即使我们遵循“创建存储过程并传递字段以将其作为参数进行搜索”的原则,SQLi仍然会执行。这是因为我们只是在存储过程中复制了不良的编程习惯。解决该问题的方法是如下重写我们的存储过程:

alter procedure getUser
 @name varchar(20)
,@pass varchar(20)
as
select usrID, usrUName, usrFullName, usrRoleID 
from Users 
where usrUName = @name and usrPass = @pass

我要说的是,开发人员必须首先了解什么是SQLi攻击以及如何进行攻击,然后相应地保护其代码。盲目遵循“最佳实践”并不总是更安全的方法……也许这就是为什么我们拥有如此多“最佳实践”的原因!


我能理解你的观点,对此我感到内gui。有时需要使用参数串联来创建动态SQL查询。您如何建议我去解决?
TheProvost

@TheProvost是个好问题。考虑一下sp_executesql:msdn.microsoft.com/en-us/library/ms188001.aspx
Tim

@Tim嗨蒂姆。我是动态SQL的新手。sp_executesql和EXECUTE(@SqlQuery)
TheProvost

2
我认为这篇文章很好地说明了一个简单的示例:codeproject.com/Tips/586207/…-但基本上,EXECUTE(@SqlQuery)并不能阻止SQL注入,但是sp_executesql(@SqlQuery,...,...)确实阻止了它。微软文章中的示例应该有所帮助。
蒂姆(Tim)

蒂姆(Tim)有TheProvost ...的解决方案;;)您可以将sp_executesql(@QUERY,@PARAMETERS,@VARS)...用于动态SQL情况...;)
Andreas Venieris

5

是的,至少在理论上,使用预处理语句会停止所有SQL注入。实际上,参数化语句可能不是真正的预备语句,例如PDO,PHP默认情况下会对其进行模拟,因此它很容易受到边缘攻击

如果您使用的是真正的预备语句,那么一切都是安全的。好吧,至少只要您不将不安全的SQL连接到您的查询中,以作为对例如无法准备表名的反应。

如果是,为什么还有那么多成功的SQL注入?仅仅因为某些开发人员太笨了而无法使用参数化语句?

是的,教育是这里的重点,也是传统代码库。不幸的是,许多教程都使用了转义功能,而这些功能很难从网上删除。


1
链接的答案实际上与准备好的语句无关。
您的常识

1
@YourCommonSense:它是关于参数化查询的,它们可能不是实际的准备,而是根据所使用的驱动程序进行模拟的。重要的是要知道并与之建立紧密的联系……
kelunik

1
同一页面上的其他答案也得到很好的评价:“如果对所有查询进行了参数设置,则还可以防止第二次注入。第一次注入会忘记用户数据是不可信的。第二次注入会忘记数据库数据是可信任的。不可靠(因为它最初来自用户)。”
罗德里戈(Rodrigo)

@kelunik链接的答案也不是关于参数化查询的问题,而是关于实质上伪造它们的库的问题。参数化查询是使用单独的参数值发送到服务器的查询。
Panagiotis Kanavos

@PanagiotisKanavos:我很清楚该答案的内容。这只是一个示例(也是一个非常常见的示例),您使用的参数化查询实际上可能未实现为

3

我在编程时避免使用绝对值。总是有例外。我强烈建议存储过程和命令对象。我的大部分背景是使用SQL Server,但我有时会玩MySql。存储过程有很多优点,包括缓存的查询计划;是的,这可以通过参数和内联SQL来完成,但是这为注入攻击开辟了更多可能性,并且无助于分离关注点。对我来说,保护数据库也容易得多,因为我的应用程序通常仅具有所述存储过程的执行权限。如果没有直接的表/视图访问,则注入任何东西都变得更加困难。如果应用程序用户受到威胁,则仅有权执行准确的预定义内容。

我的两分钱。


这与问题有什么关系?您将如何调用并将参数传递给存储过程?使用字符串连接还是使用参数化查询?再说-如果有人使用了字符串连接的存储过程创建一个“动态”查询?仅仅因为它是一个存储过程并不意味着它更安全
Panagiotis Kanavos 2015年

通常,我使用命令对象,并且还避免设计使然运行“动态查询”。
德里克(Derek)

2

我不会说“哑巴”。

我认为这些教程就是问题所在。大多数SQL教程,书籍,无论用什么解释,都用内联值解释SQL,根本没有提到绑定参数。从这些教程中学习的人们没有机会正确学习它。


2
还不够。人们为什么不使用框架或某些规范?为什么他们不使用某些愚蠢的测试人员来测试“哑弹”?因为有时候老板的薪水不高,或者他付给您X钱给一个项目,您需要从一个项目运行到另一个项目,然后从一个项目运行到另一个项目才能获得一些钱。您必须越来越快。编码器压力过大且过分按压,因此代码可以正常工作,但是编写得不好。
jedi 2015年

2

由于大多数代码在编写时都没有考虑到安全性和管理性,因此,在添加功能(尤其是可见的可以出售的东西)和安全性/稳定性/可靠性(很难出售)之间做出选择之后,他们几乎总是会选择前任的。安全仅在成为问题时才被关注。


2

参数化语句可以停止所有SQL注入吗?

是的,只要您的数据库驱动程序为每个可能的SQL文字提供一个占位符大多数准备好的语句驱动程序不这样做。假设您永远找不到字段名或值数组的占位符。这将使开发人员可以依靠串联和手动格式化来手工定制查询。具有预期的结果。

这就是为什么我将Mysql包装器用于PHP,该包装器支持大多数可以动态添加到查询中的文字,包括数组和标识符。

如果是,为什么还有那么多成功的SQL注入?仅仅因为某些开发人员太笨了而无法使用参数化语句?

如您所见,实际上,即使您并不笨拙,也无法对所有查询进行参数化。


如果所有查询都已参数化(来自用户数据还是数据库数据),那么您似乎已受到保护,如此处投票最多的评论所述:stackoverflow.com/a/134138/1086511
Rodrigo

我有点想问您的意见,只是因为您看起来足够合理。我认为如果我在其他地方阅读的内容成立,您的方法将不是最好的。无论如何,如果您改进了“使用常规工具无法将所有查询参数化”,我会很高兴。
罗德里戈(Rodrigo)

我开始阅读您的答案,直到我想到了识别“ SQL文字”的想法。这个想法对我来说似乎不太正确(似乎工作过度)。如果参数化查询确实避免了PHP中的注入(我仍在研究),那么下一步就是避免javascript注入。然后,我会回来研究您的解决方案。另外,我正在使用postgres,也许您的解决方案是mysql特定的?
罗德里戈(Rodrigo)

好的,现在(再次)我读了它,并且不认为“不可能将所有查询参数化”是一种改进。在MySQL中这不可能吗?在PostgreSQL中也不可能吗?为什么?我的PHP脚本之外是否有任何查询?哪里?我认为按标识符的意思是您试图从$ _POST数组中剥离出来的保留字?对我来说,这似乎不是路要走(直觉上,我当然是错的)。另外,我不理解“您是否尝试过绑定它?” 绑定什么?
罗德里戈(Rodrigo)

就像我想的那样,在网络上找到它并不容易。如果可以的话,请添加参考。
罗德里戈(Rodrigo)

2

首先,我对第一个问题的回答是:是的,据我所知,通过使用参数化查询,将无法再进行SQL注入。关于您的以下问题,我不确定,只能就原因提出您的看法:

我认为通过将一些不同的部分(甚至取决于某些逻辑检查)与要插入的值连接起来,“仅”编写SQL查询字符串会更容易。它只是创建查询并执行它。另一个优点是,您可以打印(回显,输出或其他方式)sql查询字符串,然后将该字符串用于对数据库引擎的手动查询。

使用准备好的语句时,您至少还要再走一步:必须构建查询(当然包括参数),必须在服务器上准备查询,必须将参数绑定到所需的实际值用于查询您必须执行查询。

这需要更多的工作(并且编程起来并不那么简单),尤其是对于某些“快速而肮脏的”工作,这些工作通常被证明是长期存在的。

最好的祝福,


2

SQL注入是较大的代码注入问题的子集,在较大的代码注入问题中,数据和代码是通过同一通道提供的,并且数据被误认为是代码。参数化查询通过使用有关什么是数据和什么是代码的上下文构成查询来防止这种情况的发生。

在某些特定情况下,这还不够。在许多DBMS中,可以通过存储过程动态执行SQL,从而在DBMS级别引入SQL注入缺陷。使用参数化查询调用此类存储过程不会阻止过程中的SQL注入被利用。在此博客文章中可以看到另一个示例。

更常见的是,开发人员错误地使用了该功能。通常,正确完成后,代码如下所示:

db.parameterize_query("select foo from bar where baz = '?'", user_input)

一些开发人员将字符串连接在一起,然后使用参数化查询,该查询实际上并未进行上述数据/代码区分,从而提供了我们正在寻找的安全性保证:

db.parameterize_query("select foo from bar where baz = '" + user_input + "'")

正确使用参数化查询可对SQL注入攻击提供非常强大(但不可渗透)的保护。


1

要保护您的应用程序免受SQL注入,请执行以下步骤:

步骤1.约束输入。步骤2.在存储过程中使用参数。步骤3.将参数与动态SQL一起使用。

请参阅http://msdn.microsoft.com/en-us/library/ff648339.aspx


8
仅存储过程实际上并没有帮助。就像在客户端代码中一样,可以在存储过程中动态构造查询字符串。
菲尔·米勒

@Fahad我可能将#2改写为“在查询和存储过程中使用参数化语句”。+1表示Novelocrat的评论,即使用不带参数的存储过程不会带来多少收益。
马修

1

即使在整个Web应用程序自己的代码中正确使用了准备好的语句,但是如果数据库代码组件以不安全的方式从用户输入构造查询,则SQL注入漏洞可能仍然存在。下面是一个存储过程的示例,该存储过程易受@name参数的SQL注入攻击:

CREATE PROCEDURE show_current_orders
(@name varchar(400) = NULL)
AS
DECLARE @sql nvarchar(4000)
SELECT @sql = ‘SELECT id_num, searchstring FROM searchorders WHERE ‘ +
‘searchstring = ‘’’ + @name + ‘’’’;
EXEC (@sql)
GO

即使应用程序以安全的方式将用户提供的名称值传递给存储过程,该过程本身也会将其直接连接到动态查询中,因此很容易受到攻击。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.