如果是,为什么还有那么多成功的SQL注入?仅仅因为某些开发人员太笨了而无法使用参数化语句?
Answers:
我在评论中张贴的对该问题的链接很好地说明了这个问题。下面总结了我对问题仍然存在的原因的看法:
刚开始的人可能不了解SQL注入。
有些人知道SQL注入,但认为转义是(唯一的)解决方案。如果您对Google进行快速搜索php mysql query
,则显示的第一页是该mysql_query
页面,该页面上有一个示例,该示例显示了将转义的用户输入插入到查询中。没有提到(至少我看不到)使用准备好的语句。就像其他人所说的那样,有太多使用参数插值的教程,因此使用频率仍然不足为奇。
缺乏对参数化语句如何工作的理解。有人认为这只是逃避价值的一种幻想手段。
其他人知道参数化的语句,但不要使用它们,因为他们听说它们太慢了。我怀疑许多人听到过如此缓慢的参数化陈述,但实际上并没有对他们自己进行任何测试。正如比尔·卡文(Bill Karwin)在演讲中所指出的那样,在考虑使用准备好的陈述时,应该很少将绩效差异作为因素。一次准备,多次执行的好处经常被忘记,安全性和代码可维护性方面的改善也常常被人们遗忘。
有些在各处都使用参数化的语句,但会插值未检查的值,例如表和列名称,关键字和条件运算符。动态搜索(例如,允许用户指定许多不同搜索字段,比较条件和排序顺序的搜索)就是其中的主要示例。
使用ORM时有错误的安全感。ORM仍然允许对SQL语句部分进行插值-参见5。
编程是一个大而复杂的主题,数据库管理是一个大而复杂的主题,安全性是一个大而复杂的主题。开发安全的数据库应用程序并非易事-即使是经验丰富的开发人员也可能会陷入困境。
关于stackoverflow的许多答案都无济于事。当人们编写使用动态SQL和参数插值的问题时,通常缺少建议使用参数化语句的答案。在某些情况下,我曾有人反对我使用准备好的语句的建议-通常是因为人们认为性能开销不可接受。我严重怀疑那些问大多数这些问题的人所处的位置,即准备参数化语句所花费的额外几毫秒会对其应用造成灾难性的影响。
当文章谈论停止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攻击。
好问题。答案比确定性更随机,我将尝试通过一个小例子来解释我的观点。
网上有很多参考文献建议我们在查询中使用参数或对参数使用存储过程,以避免使用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攻击以及如何进行攻击,然后相应地保护其代码。盲目遵循“最佳实践”并不总是更安全的方法……也许这就是为什么我们拥有如此多“最佳实践”的原因!
是的,至少在理论上,使用预处理语句会停止所有SQL注入。实际上,参数化语句可能不是真正的预备语句,例如PDO
,PHP默认情况下会对其进行模拟,因此它很容易受到边缘攻击。
如果您使用的是真正的预备语句,那么一切都是安全的。好吧,至少只要您不将不安全的SQL连接到您的查询中,以作为对例如无法准备表名的反应。
如果是,为什么还有那么多成功的SQL注入?仅仅因为某些开发人员太笨了而无法使用参数化语句?
是的,教育是这里的重点,也是传统代码库。不幸的是,许多教程都使用了转义功能,而这些功能很难从网上删除。
我在编程时避免使用绝对值。总是有例外。我强烈建议存储过程和命令对象。我的大部分背景是使用SQL Server,但我有时会玩MySql。存储过程有很多优点,包括缓存的查询计划;是的,这可以通过参数和内联SQL来完成,但是这为注入攻击开辟了更多可能性,并且无助于分离关注点。对我来说,保护数据库也容易得多,因为我的应用程序通常仅具有所述存储过程的执行权限。如果没有直接的表/视图访问,则注入任何东西都变得更加困难。如果应用程序用户受到威胁,则仅有权执行准确的预定义内容。
我的两分钱。
我不会说“哑巴”。
我认为这些教程就是问题所在。大多数SQL教程,书籍,无论用什么解释,都用内联值解释SQL,根本没有提到绑定参数。从这些教程中学习的人们没有机会正确学习它。
参数化语句可以停止所有SQL注入吗?
是的,只要您的数据库驱动程序为每个可能的SQL文字提供一个占位符。大多数准备好的语句驱动程序不这样做。假设您永远找不到字段名或值数组的占位符。这将使开发人员可以依靠串联和手动格式化来手工定制查询。具有预期的结果。
这就是为什么我将Mysql包装器用于PHP,该包装器支持大多数可以动态添加到查询中的文字,包括数组和标识符。
如果是,为什么还有那么多成功的SQL注入?仅仅因为某些开发人员太笨了而无法使用参数化语句?
如您所见,实际上,即使您并不笨拙,也无法对所有查询进行参数化。
首先,我对第一个问题的回答是:是的,据我所知,通过使用参数化查询,将无法再进行SQL注入。关于您的以下问题,我不确定,只能就原因提出您的看法:
我认为通过将一些不同的部分(甚至取决于某些逻辑检查)与要插入的值连接起来,“仅”编写SQL查询字符串会更容易。它只是创建查询并执行它。另一个优点是,您可以打印(回显,输出或其他方式)sql查询字符串,然后将该字符串用于对数据库引擎的手动查询。
使用准备好的语句时,您至少还要再走一步:必须构建查询(当然包括参数),必须在服务器上准备查询,必须将参数绑定到所需的实际值用于查询您必须执行查询。
这需要更多的工作(并且编程起来并不那么简单),尤其是对于某些“快速而肮脏的”工作,这些工作通常被证明是长期存在的。
最好的祝福,
框
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注入攻击提供非常强大(但不可渗透)的保护。
即使在整个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
即使应用程序以安全的方式将用户提供的名称值传递给存储过程,该过程本身也会将其直接连接到动态查询中,因此很容易受到攻击。