如果是,为什么还有那么多成功的SQL注入?仅仅因为一些开发人员太愚蠢而无法使用参数化语句?
答案 0 :(得分:58)
我在评论中发布的链接很好地解释了问题。我总结了我对问题持续存在的看法,下面是:
刚开始的人可能没有注意到SQL注入。
有些人知道SQL注入,但认为转义是(唯一的?)解决方案。如果您对php mysql query
进行快速Google搜索,则显示的第一个页面是mysql_query
页面,其中有一个示例显示将转义的用户输入插入到查询中。没有提及(至少不是我能看到)使用预准备语句。正如其他人所说,那里有很多使用参数插值的教程,使用它的频率并不令人惊讶。
缺乏对参数化语句如何工作的理解。有些人认为这只是逃避价值观的一种奇特手段。
其他人都知道参数化语句,但不要使用它们,因为他们听说它们太慢了。我怀疑很多人都听说过多么缓慢的典型语句,但实际上并没有对自己做过任何测试。正如Bill Karwin在他的演讲中指出的那样,在考虑使用准备好的陈述时,很少会将性能上的差异用作一个因素。 准备一次,执行许多的好处,往往似乎被遗忘了,安全性和代码可维护性方面的改进也是如此。
有些人在任何地方都使用参数化语句,但是插入了未经检查的值,例如表和列名,关键字和条件运算符。动态搜索,例如允许用户指定许多不同搜索字段,比较条件和排序顺序的搜索,就是这方面的主要例子。
使用ORM时的安全感错误。 ORM仍然允许插入SQL语句部分 - 参见5.
编程是一个庞大而复杂的主题,数据库管理是一个大而复杂的主题,安全是一个大而复杂的主题。开发安全的数据库应用程序并不容易 - 即使是经验丰富的开发人员也会被抓住。
stackoverflow上的许多答案都无济于事。当人们编写使用动态SQL和参数插值的问题时,通常缺少建议使用参数化语句的响应。有几次,我让人们反驳我的建议,使用准备好的陈述 - 通常是因为认为不可接受的性能开销。我非常怀疑那些提出大部分问题的人处于准备参数化陈述所花费的额外几毫秒将对其应用产生灾难性影响的位置。
答案 1 :(得分:42)
当文章谈到停止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)
其中用户名和密码是指向相关输入的用户名和密码的变量
现在,你可能在想,这根本不会改变任何事情。当然你仍然可以在用户名字段中输入类似Nobody OR 1 = 1'的内容,从而有效地进行查询:
sqlQuery='SELECT * FROM custTable WHERE User=Nobody OR 1=1'-- AND Pass=?'
这似乎是一个有效的论点。但是,你错了。
参数化查询的工作方式是,sqlQuery作为查询发送,数据库确切知道此查询将执行的操作,然后才会将用户名和密码仅作为值插入。这意味着它们不能影响查询,因为数据库已经知道查询将执行什么操作。所以在这种情况下,它会寻找一个用户名“Nobody OR 1 = 1' - ”和一个空密码,这应该是假的。
这不是一个完整的解决方案,并且仍然需要进行输入验证,因为这不会影响其他问题,例如XSS攻击,因为您仍然可以将javascript放入数据库。然后,如果这被读出到页面上,它将显示为普通的javascript,具体取决于任何输出验证。所以最好的办法仍然是使用输入验证,但使用参数化查询或存储过程来阻止任何SQL攻击。
答案 2 :(得分:9)
好问题。 答案更具随机性而非确定性,我将尝试使用一个小例子来解释我的观点。
网上有很多引用建议我们在查询中使用参数或者使用带参数的存储过程以避免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 = '''+@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攻击是什么以及如何执行,然后相应地保护他们的代码。盲目追随最佳实践'并不总是更安全的方式...也许这就是为什么我们有这么多的最佳实践' - 失败!
答案 3 :(得分:5)
是的,使用预准备语句会停止所有SQL注入,至少在理论上是这样。实际上,参数化语句可能不是真正准备好的语句,例如PHP中的PDO
默认情况下会模仿它们,因此open to an edge case attack。
如果你使用真实的预备陈述,一切都是安全的。好吧,至少只要您不将不安全的SQL连接到查询中作为对例如无法准备表名的反应。
如果是,为什么还有那么多成功的SQL注入?仅仅因为一些开发人员太愚蠢而无法使用参数化语句?
是的,教育是这里的主要观点,也是遗留代码库。许多教程都使用转义,遗憾的是,这些教程很容易从网上删除。
答案 4 :(得分:3)
我避免编程中的绝对;总有一个例外。我强烈推荐存储过程和命令对象。我的大部分背景都是SQL Server,但我不时会使用MySql。存储过程有许多优点,包括缓存的查询计划;是的,这可以通过参数和内联SQL来完成,但这为注入攻击开辟了更多的可能性,并没有帮助分离关注点。对我来说,保护数据库也要容易得多,因为我的应用程序通常只对所述存储过程具有执行权限。没有直接的表/视图访问,注入任何东西都要困难得多。如果应用程序用户遭到入侵,则只有权执行预先定义的内容。
我的两分钱。
答案 5 :(得分:2)
我不会说“哑巴”。
我认为教程是问题所在。大多数SQL教程,书籍,无论用内联值解释SQL,都没有提到绑定参数。从这些教程中学习的人没有机会正确地学习它。
答案 6 :(得分:2)
因为大多数代码都没有考虑到安全性和管理,在添加功能(特别是可以出售的可见东西)和安全性/稳定性/可靠性(这是一个更难的销售)之间做出选择,他们几乎总是选择前者。当问题成为问题时,安全性只是一个问题。
答案 7 :(得分:2)
参数化语句可以停止所有SQL注入吗?
是的,只要您的数据库驱动程序为每个可能的SQL文字提供占位符。大多数准备好的语句驱动程序都没有。比如,您永远不会找到字段名称或值数组的占位符。这将使开发人员重新使用串联和手动格式化手动定制查询。 预测结果。
这就是为什么我为PHP制作了我的Mysql包装器,它支持大多数可以动态添加到查询中的文字,包括数组和标识符。
如果是,为什么还有那么多成功的SQL注入?仅仅因为一些开发人员太愚蠢而无法使用参数化语句?
正如您所看到的,实际上,即使您没有愚蠢,也不可能将所有参数化参数化。
答案 8 :(得分:2)
首先我回答你的第一个问题:是的,据我所知,通过使用参数化查询,SQL注入将不再可能。至于你的以下问题,我不确定,只能就原因给你我的意见:
我认为通过连接一些不同的部分(甚至可能依赖于某些逻辑检查)以及要插入的值来“简单地”编写SQL查询字符串会更容易。 它只是创建查询并执行它。 另一个优点是您可以打印(回显,输出或其他)sql查询字符串,然后使用此字符串对数据库引擎进行手动查询。
使用预准备语句时,您至少还需要更多一步: 你必须建立你的查询(当然包括参数) 您必须在服务器上准备查询 您必须将参数绑定到要用于查询的实际值 您必须执行查询。
这是一项更多的工作(而不是那么简单的编程),特别是对于一些经常被证明是非常长寿的“快速和肮脏”的工作......
致以最诚挚的问候,
箱
答案 9 :(得分:1)
要保护您的应用程序免受SQL注入,请执行以下步骤:
步骤1.限制输入。 步骤2.将参数与存储过程一起使用。 步骤3.将参数与动态SQL一起使用。
答案 10 :(得分: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注入攻击提供了非常强大但不可穿透的保护。
答案 11 :(得分:1)
即使 准备好的语句可以在整个Web应用程序中正确使用 代码,如果数据库代码组件构成,则SQL注入漏洞可能仍然存在 从用户输入中以不安全的方式进行查询。 以下是易受SQL攻击的存储过程的示例 注入@name参数:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Module Demo</title>
<link rel="stylesheet" href="https://unpkg.com/@glidejs/glide@3.2.3/dist/css/glide.core.min.css" />
<link rel="stylesheet" href="https://unpkg.com/@glidejs/glide@3.2.3/dist/css/glide.theme.min.css" />
<script type="module" src="demo.js"></script>
</head>
<body>
<main>
<div class="glide">
<div data-glide-el="track" class="glide__track">
<ul class="glide__slides">
<li class="glide__slide">Foo</li>
<li class="glide__slide">Bar</li>
<li class="glide__slide">Fizz</li>
</ul>
</div>
</div>
</main>
</body>
</html>
即使应用程序将用户提供的名称值传递给存储的 程序以一种安全的方式将其直接连接到 动态查询,因此很容易受到攻击。