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

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

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

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

  2. 有些人知道SQL注入,但认为转义是唯一的解决scheme。 如果你做一个快速的谷歌searchphp mysql query ,出现的第一个页面是mysql_query页面,其中有一个示例显示插入到查询转义的用户input。 没有提到(至less我不能看到)使用预处理语句。 正如其他人所说,有那么多的教程,使用参数插值,这是不是真的令人惊讶的频率仍然使用。

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

  4. 其他人知道参数化的陈述,但不要使用它们,因为他们听说他们太慢了。 我怀疑有很多人听说过非常缓慢的陈述性陈述,但实际上并没有对他们自己进行任何testing。 正如比尔·卡尔文(Bill Karwin)在演讲中指出的那样,在考虑使用准备好的声明时,性能上的差异很less被用作一个因素。 准备一次,执行很多的好处往往似乎被遗忘,安全性和代码可维护性的改进也是如此。

  5. 有些在任何地方都使用参数化语句,但插入了未检查的值,如表和列名称,关键字和条件运算符。 dynamicsearch(例如允许用户指定多个不同search字段,比较条件和sorting顺序的dynamicsearch)就是其中的主要例子。

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

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

  8. 在stackoverflow许多答案不帮助。 当人们编写使用dynamicSQL和参数插值的问题时,往往缺乏build议使用参数化语句的响应。 有几次,我有人反驳我的build议,使用准备好的陈述 – 通常是因为感到不可接受的性能开销。 我严重怀疑那些询问大多数这些问题的人所处的位置,即准备参数化陈述需要花费几毫秒的时间将会对其应用产生灾难性的影响。

当文章谈论停止SQL攻击的参数化查询时,他们并不真正解释为什么,通常是“它确实,所以不要问为什么”,可能是因为他们不知道自己。 一个坏教育者的确切迹象是不能承认他们不知道的东西。 但是我离题了。 当我说我发现完全可以理解,很容易混淆。 想象一下dynamicSQL查询

 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 

这就是说,select所有的用户名是空白的('')或1 = 1,这是一个布尔值,等于true。 然后它使用 – 注释掉其余的查询。 因此,这只会打印出所有的客户表,或者做任何你想做的事情,如果login,它将以第一个用户的权限login,这通常是pipe理员。

现在参数化的查询以不同的方式执行,代码如下:

 sqlQuery='SELECT * FROM custTable WHERE User=? AND Pass=?' parameters.add("User", username) parameters.add("Pass", password) 

其中用户名和密码是指向相关的input用户名和密码的variables

现在,在这一点上,你可能会想,这根本没有任何改变。 当然,你仍然可以只是把用户名字段像没有人或1 = 1' – 有效地进行查询:

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

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

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

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

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

在networking上有许多引用build议我们在查询中使用参数,或者使用存储过程的参数来避免SQL注入(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 

上述参数将作为parameter passing给存储过程,最后执行的SQL命令是:

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

..将从用户获取所有行

这里的问题是,即使我们遵循“创build存储过程并传递字段来search参数”的原则,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攻击是什么以及如何执行,然后相应地保护他们的代码。 盲目追随“最佳实践”并不总是更安全的方式…也许这就是为什么我们有这么多“最佳实践” – 失败!

是的,至less在理论上,使用准备好的语句会停止所有SQL注入。 实际上,参数化语句可能并不是真正准备好的语句,例如PHP中的PDO默认模拟它们,所以它对于边缘案例攻击是开放的 。

如果你正在使用真实的准备报表,一切都是安全的。 那么,至less只要你不把不安全的SQL连接到你的查询中,作为不能准备表名的例子。

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

是的,教育是这里的重点,遗留的代码基础。 很多教程使用转义,不幸的是,这些教程不能轻易地从networking中删除。

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

我的两分钱

因为大多数代码不是考虑到安全性和pipe理,所以在添加function(特别是可以销售的东西)和安全性/稳定性/可靠性(这是一个更难卖的东西)之间进行select时,他们几乎总是select前任的。 安全问题只是一个问题。

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

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

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

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

正如你所看到的,事实上,即使你不是愚蠢的,也不可能让所有的查询参数化。

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

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

使用预处理语句时,您总是至less还有一个步骤:您必须构build查询(当然包括参数)您必须在服务器上准备查询您必须将参数绑定到所需的实际值用于查询您必须执行查询。

对于一些经常被certificate是非常长寿的“快速和肮脏”的工作来说,这是更多的工作(而不是那么直接的编程)。

最好的祝福,

我不会说“笨蛋”。

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

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

步骤1.限制input。 步骤2.使用存储过程的参数。 第3步。使用dynamicSQL参数。

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

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

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

更常见的情况是,开发人员使用错误的function。 通常,如果正确完成,代码看起来像这样:

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

有些开发人员会将string连接在一起,然后使用参数化查询,这实际上并没有提供前面提到的数据/代码区分,这提供了我们正在寻找的安全保证:

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

参数化查询的正确使用为防范SQL注入攻击提供了非常强大但并非难以避免的保护。