为什么不让非参数化查询返回错误呢?


22

SQL注入是一个非常严重的安全性问题,很大程度上是因为它很容易弄错:显而易见的,直观的方法来构建包含用户输入的查询,这使您容易受到攻击,而减轻它的正确方法则需要您了解参数化查询和SQL注入优先。

在我看来,解决此问题的显而易见的方法是关闭一个显而易见的(但错误的)选项:修复数据库引擎,以便接收到的所有在其WHERE子句中使用硬编码值而不是参数的查询都返回一个漂亮的,描述性的错误消息,指示您改为使用参数。显然,这需要有一个退出选项,这样管理工具中的临时查询之类的东西仍然可以轻松运行,但是默认情况下应启用它。

这样做将关闭SQL注入冷服务,几乎在一夜之间,但是据我所知,实际上没有RDBMS这样做。有什么理由不这样做吗?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'在单个查询中将同时具有硬编码和参数化的值–尝试抓住这一点!我认为此类混合查询有有效的用例。
阿蒙2015年

6
如何从今天选择记录SELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
杰德

10
@MasonWheeler抱歉,我的意思是“尝试允许这样做”。请注意,它是完全参数化的,不会受到SQL注入的影响。但是,数据库驱动程序无法判断该文字"bad"是真正的文字还是由字符串串联产生。我看到的两个解决方案要么是摆脱SQL和其他嵌入字符串的DSL(请是),要么是推广使用字符串连接比使用参数化查询更令人讨厌的语言(umm,否)。
阿蒙2015年

4
RDBMS将如何检测是否执行此操作?一夜之间,就不可能使用交互式SQL提示访问RDBMS。。。您将完全无法使用任何工具输入DDL或DML命令。
jwenting

8
从某种意义上讲,您可以这样做:根本不要在运行时构造SQL查询,而要使用ORM或其他一些抽象层,从而避免构造SQL查询。ORM没有您需要的功能吗?然后,SQL是一种用于想要编写SQL的人的语言,这就是为什么它总体上允许他们编写SQL的原因。根本的问题是,动态生成代码比看起来要难,但是人们无论如何都希望这样做,并且会对那些不允许使用它们的产品不满意。
史蒂夫·杰索普

Answers:


45

在很多情况下,使用文字是正确的方法。

从性能的角度来看,有时需要在查询中使用文字。想象一下,我有一个错误跟踪器,一旦它变得足够大而不必担心性能,我预计系统中70%的错误将被“关闭”,20%的将被“打开”,5%的将被“激活”,并且5 %将处于其他状态。我可能很想让查询返回所有活动的错误

SELECT *
  FROM bug
 WHERE status = 'active'

而不是将其status作为绑定变量传递。我想要一个不同的查询计划,具体取决于传入的值status-我想执行表扫描以返回已关闭的错误,并在status列以返回有效贷款。现在,不同的数据库和不同的版本具有不同的方法来(或多或少成功)允许同一查询根据绑定变量的值使用不同的查询计划。但这往往会引入相当多的复杂性,需要对这些复杂性进行管理,以平衡是否需要重新解析查询或是否将现有计划重新用于新的绑定变量值的决定。对于开发人员来说,处理这种复杂性可能是有意义的。或者,当我比优化器更了解关于数据的外观的信息时,强制使用不同的路径也很有意义。

从代码复杂性的角度来看,在SQL语句中包含文字也很有意义。例如,如果您的某zip_code列包含5个字符的邮政编码,有时还包含另外4位数字,那么执行类似

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

而不是为数值传递4个单独的参数。这些并不是永远不会改变的事情,因此使它们绑定变量只会使代码变得更难阅读,并有可能使人以错误的顺序绑定参数并最终导致错误。


12

当通过将来自不受信任和未经验证的源中的文本与查询的其他部分连接而构建查询时,就会发生SQL注入。尽管这种情况最常发生在字符串文字中,但这并不是它发生的唯一方式。查询数值可能会使用用户输入的字符串(应该只包含数字),并与其他材料连接起来以形成查询,而通常没有与字符串文字相关联的引号。过度信任客户端验证的代码可能具有类似字段名称之类的东西,它们来自HTML查询字符串。查看SQL查询字符串的代码无法看到其组装方式。

重要的不是SQL语句是否包含字符串文字,而是字符串是否包含不可信来源的任何字符序列,对此的验证最好在构建查询的库中处理。在C#中,通常没有办法编写允许字符串文字但不允许其他类型的字符串表达式的代码,但是可能会有一种编码实践规则,要求使用查询构建类而不是查询构建类来构建查询。字符串串联,并且任何将非文字字符串传递给查询构建器的人都必须证明这种操作的合理性。


1
作为“它是文字上的”的近似值,您可以检查字符串是否为interned。
CodesInChaos

1
@CodesInChaos:是的,只要有理由在运行时生成字符串的任何人都使用接受非文字字符串的方法,而不是在运行时生成的字符串中进行交互并使用(给非文字字符串方法一个不同的名称将使代码审阅者易于检查它的所有使用)。
2015年

请注意,尽管在C#中无法做到这一点,但其他一些语言确实具有使之成为可能的功能(例如,Perl的污染字符串模块)。
2015年

简而言之,这是一个客户端问题,而不是服务器问题。
Blrfl 2015年

7
SELECT count(ID)
FROM posts
WHERE deleted = false

如果要将这些结果放在论坛的页脚中,则需要添加一个虚拟参数,每次都只说false。或天真的Web程序员查找如何禁用该警告,然后继续。

现在您可以说您将为枚举添加一个例外,但这只会再次打开漏洞(尽管更小)。更不用说首先要教育人们不要使用varchars那些东西。

注入的真正问题是以编程方式构造查询字符串。解决方案是使用存储过程机制并强制其使用或允许的查询白名单。


2
如果您的解决方案“太容易忘记-或一开始就不知道-使用参数化查询”的解决方案是“让所有人都记住-并首先知道-使用存储的procs”,那么您遗漏了整个问题的重点。
梅森惠勒2015年

5
我在工作中已经看到通过存储过程进行SQL注入。事实证明,对所有东西强制执行存储过程都是错误的。始终有0.5%是真正的动态查询(您无法参数化整个where子句,更不用说表联接了)。
2015年

在此答案的示例中,您可以替换deleted = falseNOT deleted,避免使用文字。但是这一点通常是正确的。
psmears,2015年

5

TL; DR:您必须限制所有文字,而不仅限于WHERE子句中的文字。由于它们不这样做的原因,它允许数据库保持与其他系统的分离。

首先,您的前提存在缺陷。您只想限制WHERE子句,但这不是用户输入可以进入的唯一位置。例如,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

这同样容易受到SQL注入的攻击:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

因此,您不能只在WHERE子句中限制文字。您必须限制所有文字。

现在剩下的问题是:“为什么根本不使用文字?” 请记住这一点:虽然在大多数情况下,关系数据库是在用另一种语言编写的应用程序下使用的,但并不要求必须使用应用程序代码才能使用该数据库。在这里,我们有一个答案:您需要文字来编写代码。唯一的其他选择是要求所有代码以某种独立于数据库的语言编写。因此拥有它们使您能够直接在数据库中编写“代码”(SQL)。这是很有价值的去耦,没有文字就不可能。(尝试在没有文字的情况下有时用自己喜欢的语言编写。我敢肯定,您可以想象这会有多困难。)

作为一个常见示例,常在值列表/查找表的填充中使用文字:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

没有它们,您仅需要使用另一种编程语言编写代码即可填充该表。直接在SQL中执行此操作的功能很有价值

然后,我们还有一个问题:为什么编程语言客户端库不这样做呢?在这里,我们有一个非常简单的答案:他们将为每个受支持的数据库版本重新实现整个数据库解析器。为什么?因为没有其他方法可以保证您找到了所有文字。正则表达式还不够。例如:PostgreSQL中包含4个独立的文字:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

尝试这样做将是维护方面的噩梦,尤其是由于有效语法经常在主要版本的数据库之间更改时。

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.