2011-07-22 38 views
60

如果是,为什么还有这么多成功的SQL注入?仅仅因为一些开发人员太愚蠢,无法使用参数化语句?参数化语句可以停止所有SQL注入吗?

+5

这是一个很好的问题,绝对可怕的答案(作为我评论的时间) – Ibu

+0

我希望至少有良好的15k声望的人或者具有良好经验的人可以为这个问题增加宝贵的意见。 – Ibu

+3

请参阅[Bill Karwin's](http://stackoverflow.com/users/20860/bill-karwin)* Sql注入神话和谬论* [talk](http://www.ustream.tv/recorded/10772454)和[幻灯片](http://www.slideshare.net/billkarwin/sql-injection-myths-and-fallacies)了解关于此主题的更多信息。他解释了什么是SQL注入,如何逃避通常不够好,以及如何降低存储过程和参数化语句。 – Mike

回答

49

我发表在我对问题的评论中的链接很好地解释了这个问题。我总结了我的感受,为什么问题仍然存在,如下:

  1. 刚刚开始的人可能没有意识到SQL注入。

  2. 有些人知道SQL注入,但认为转义是(唯一的?)解决方案。如果您快速执行Google搜索php mysql query,则首先出现的页面是mysql_query页面,其中有一个示例显示将查询中的已转义用户输入内插。没有提到(至少我没有看到)使用预处理语句。正如其他人所说,有那么多的使用参数插值的教程,它仍然被使用的频率并不令人惊讶。

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

  4. 其他人知道参数化语句,但不使用它们是因为他们听说他们太慢了。我怀疑有很多人听说过非常缓慢的参数化陈述,但实际上并没有对他们自己进行任何测试。正如Bill Karwin在演讲中指出的那样,在考虑使用准备好的声明时,性能差异很少被用作一个因素。 的好处就是准备一次,执行很多,经常看起来是被遗忘的,就像安全性和代码可维护性的改进一样。

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

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

  7. 编程是一个大而复杂的话题,数据库管理是一个大而复杂的话题,安全是一个大而复杂的课题。开发安全的数据库应用程序并不容易 - 即使有经验的开发人员也可能被抓住。

  8. 许多计算器上的答案没有帮助。当人们编写使用动态SQL和参数插值的问题时,往往缺乏建议使用参数化语句的响应。有几次,我有人反驳我的建议,使用准备好的陈述 - 通常是因为感觉到不可接受的性能开销。我严重怀疑那些询问大多数这些问题的人是否处于准备参数化陈述所需的额外几毫秒会对其应用产生灾难性影响的位置。

2

我不会说“哑”。

我认为教程是问题所在。大多数SQL教程,书籍,无论如何解释带有内联值的SQL,根本不提及绑定参数。从这些教程中学习的人没有机会正确地学习它。

+2

这不够。为什么人们不使用框架或某些orm?为什么他们不用一些愚蠢的测试人员来测试“哑注入”?因为有时老板不会很好地付钱,或者他为一个项目支付了X钱,你需要从一个项目运行到另一个项目,并从一个项目运行到另一个项目以获得一些资金。你必须更快,更快。编码器受到压力并被过度按压,因此代码可以正常工作,但编写不好。 – jedi

1

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

步骤1.约束输入。 第2步。使用存储过程的参数。 第3步。使用动态SQL的参数。

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

+7

单独存储过程实际上并不是一个帮助。可以在存储过程中动态构造查询字符串,就像在客户端代码中一样。 – Novelocrat

+0

@Fahad我可能会将#2改写为“在查询和存储过程中使用参数化语句”。 +1给Novelocrat的评论说使用没有参数的存储过程并没有太大的帮助。 – Matt

2

因为大多数代码是没有考虑到安全性和管理的书面,给予增加功能(尤其是可见的东西可以出售)和安全/稳定/可靠性之间作出选择(这是更难卖),他们几乎总是会选择前者。安全问题只是一个问题。

5

是的,使用预准备语句会停止所有SQL注入,至少在理论上是如此。实际上,参数化语句可能不是真实准备的语句,例如, PHP中的PDO默认模拟它们,因此它是open to an edge case attack

如果你使用真正的准备语句,一切都是安全的。那么,至少只要你不把不安全的SQL连接到你的查询中就可以作为例如不能准备表名的反应。

如果是,为什么仍然有这么多成功的SQL注入?仅仅因为一些开发人员太愚蠢,无法使用参数化语句?

是的,教育是这里的主要观点和遗留代码的基础。很多教程使用转义,不幸的是,这些教程不能轻易从网络中删除。

+1

链接的答案实际上与准备的陈述无关。 –

+1

@YourCommonSense:这是关于参数化查询,它们可能不是实际的准备,而是根据使用的驱动程序进行仿真。了解并非常重要... – kelunik

+1

在同一页面中的其他答案有一个非常好的评论:“如果所有查询都是参数化的,那么您也可以免受二阶注入的影响,一阶注入会忘记用户数据是不可信的,二阶注入是忘记数据库数据是不可信的(因为它最初来自用户)“。 – Rodrigo

28

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

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

这么一个简单的SQL注入将只是把用户名作为“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) 

其中用户名和密码是指向相关的输入的用户名和密码

现在,在这一点上的变量,你可能会想,这根本不会改变任何事情。当然,你仍然可以只投入了用户名字段像没有人OR 1 = 1' - ,有效地使查询:

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

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

参数化查询的工作方式是将sqlQuery作为查询发送,并且数据库确切知道此查询将执行什么操作,只有这样才会将用户名和密码作为值插入。这意味着它们不能影响查询,因为数据库已经知道查询将执行什么操作。因此,在这种情况下,它会查找“无人或1 = 1”的用户名 - “和一个空白的密码,它应该是错误的。

虽然这不是一个完整的解决方案,输入验证仍然需要完成,因为这样不会影响其他问题,如XSS攻击,因为您仍然可以将JavaScript放入数据库。然后,如果这是读出到一个页面上,它会显示为正常的JavaScript,这取决于任何输出验证。所以最好的做法仍然是使用输入验证,但使用参数化查询或存储过程来阻止任何SQL攻击。

+0

这增加了我所寻找的内容,但是你能否更详细地解释你会做什么为“输入验证?”您还提到,查询可能会发生其他攻击,例如XSS,但是您能否解释这将如何发生?所以,基本上,我们将如何完全防范SQL注入,或者我们是否在所有类型的注入中都有所爱好?谢谢。 – XaolingBao

+1

@JosipIvic:鉴于有多少人询问参数化陈述是如何工作的,看到如此之少 - 如果有其他人真的 - 像你一样分解答案是令人震惊的。感谢你用一个非常直观的例子来写出这样一个清晰的解释。 – daOnlyBG

+0

辉煌。一个例子就像他们说的那样描绘了千言万语! – Ryan

2

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

是的,只要您的数据库驱动程序为每个可能的SQL字面量提供占位符大多数准备好的语句驱动程序不。说,你永远不会找到一个字段名称或值的数组占位符。这将使开发人员重新使用串联和手动格式来手动修改查询。 预测结果。

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

如果是,为什么仍然有这么多成功的SQL注入?仅仅因为一些开发人员太愚蠢,无法使用参数化语句?

正如你所看到的,在现实中,它只是不可能有所有你的查询参数,即使你不是哑巴。

+0

如果所有的查询都是参数化的(来自用户数据或来自数据库数据),那么看起来您受到了保护,正如最受评论的评论中所述:http://stackoverflow.com/a/134138/1086511 – Rodrigo

+0

我有点问你的意见,只是因为你看起来够合理。如果我在其他地方读过的话,我认为你的方法不会是最好的。无论如何,如果你对“使用常规工具无法让所有查询参数化”进行改进,我一定会喜欢。 – Rodrigo

+0

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

8

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

网络上有很多参考文献建议我们在查询中使用参数或使用存储过程的参数以避免SQL注入(SQLi)。我会告诉你存储过程(例如)不是针对SQLi的魔术棒。责任仍然在程序员身上。

考虑下面的SQL Server存储过程会从表“用户”获取用户行:

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 = '''[email protected]+''' and usrPass = '''[email protected]+'''' 
execute(@sql) 

你可以通过传递作为参数的用户名和密码获得满意的结果。假设密码是在自由文本(只是在这个例子中的简单)正常呼叫将是:

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

EXECUTE @RC = [dbo].[getUser] 
    @name = 'admin' 
    ,@pass = '[email protected]@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攻击以及如何执行,然后,以保障他们的代码因此。盲目追随'最佳实践'并不总是更安全的方式...也许这就是为什么我们有这么多'最佳实践' - 失败!

+0

我可以理解你的观点,我对此感到内疚。有时需要动态sql查询创建,我使用了参数串联。你会如何建议我去解决它? – TheProvost

+0

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

+0

@Tim嗨tim。我新来动态SQL。 sp_executesql和EXECUTE(@SqlQuery) – TheProvost

2

首先我回答你的第一个问题:是的,据我所知,通过使用参数化查询,SQL注入将不再可能。至于你的下面的问题,我不知道,只能给你我的意见,原因如下:

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

当使用预处理语句的工作,你总是有至少一个步骤的详细: 你必须构建查询(包括参数,当然) 你必须在服务器 上准备查询你必须绑定参数设置为要用于查询的实际值 您必须执行查询。

这是有点更多的工作(而不是如此直白地计划),尤其是对一些“快速和肮脏”的工作往往被证明是非常长寿...

最好的问候,

3

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

我的两分钱。

+0

这与问题有何关系?你打算如何调用并传递参数给存储过程?使用字符串连接还是使用参数化查询?此外 - 如果某人在存储过程中使用字符串连接*来创建“动态”查询会怎么样?仅仅因为它是一个存储过程并不意味着它更安全 –

+0

通常我使用一个命令对象,并且还避免了按设计运行“动态查询”。 – Derek

1

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

在某些特定情况下,这是不够的。在许多DBMS中,可以使用存储过程动态执行SQL,在DBMS级别引入SQL注入缺陷。使用参数化查询调用这样的存储过程不会阻止程序中的SQL注入被利用。另一个例子可以在this blog post中看到。

更常见的是,开发人员错误地使用了该功能。

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

一些开发商将字符串连接在一起,然后使用参数化查询,这并不能使上述数据/代码的区别在于提供了安全保证我们”:一般在做正确的代码看起来是这样的寻找:

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

参数化查询的正确使用提供了非常强大但并非不可阻挡的防范SQL注入攻击的保护。