SQL查询参数化如何工作?

时间:2009-08-11 21:45:18

标签: sql security sql-injection

我觉得有点傻,因为我似乎是世界上唯一没有得到它的人,但无论如何都要去。我将以Python为例。当我使用原始SQL查询(我通常使用ORM)时,我使用参数化,就像使用SQLite的这个例子一样:

方法A:

username = "wayne"
query_params = (username)
cursor.execute("SELECT * FROM mytable WHERE user=?", query_params)

我知道这有效,我知道这是一般推荐的方法。一个SQL注入易受影响的方法来执行相同的操作将是这样的:

方法B:

username = "wayne"
cursor.execute("SELECT * FROM mytable WHERE user='%s'" % username)

到目前为止,我可以告诉我理解SQL注入,如this Wikipedia article中所述。我的问题很简单:方法A与方法B有什么不同?为什么方法A的最终结果与方法B不同?我假设cursor.execute()方法(Python的DB-API规范的一部分)负责正确转义和输入类型检查,但这在任何地方都没有明确说明。在这种情况下,这就是所有参数化吗?对我来说,当我们说“参数化”时,所有这些都意味着“字符串替换”,如%-formatting。这是不正确的吗?

5 个答案:

答案 0 :(得分:41)

参数化查询实际上不会进行字符串替换。如果使用字符串替换,则SQL引擎实际上会看到类似

的查询
SELECT * FROM mytable WHERE user='wayne'

如果使用?参数,则SQL引擎会看到类似

的查询
SELECT * FROM mytable WHERE user=<some value>

这意味着在它甚至看到字符串“wayne”之前,它可以完全解析查询并通常了解查询的作用。它将“wayne”粘贴到自己的查询表示中,而不是描述查询的SQL字符串。因此,SQL注入是不可能的,因为我们已经通过了该过程的SQL阶段。

(以上是概括的,但它或多或少传达了这个想法。)

答案 1 :(得分:4)

当您进行文本替换时(如方法B),您必须警惕引号等,因为服务器将获得单个文本,并且必须确定值的结束位置。

使用参数化语句OTOH,DB服务器按原样获取语句,不带参数。使用简单的二进制安全协议将值作为不同的数据发送到服务器。因此,您的程序不必在值周围加上引号,当然,如果值本身已经有引号也无关紧要。

类比是关于源代码和编译代码:在方法B中,您正在构建过程的源代码,因此您必须确保严格遵循语言语法。使用方法A,首先构建并编译一个过程,然后(在您的示例中紧接着),使用您的值作为参数调用该过程。当然,内存中的值不受语法限制。

嗯......这不是一个真正的类比,它真的发生在幕后(大致)。

答案 2 :(得分:2)

使用参数化查询是解决数据库客户端库转义和防止注入任务的好方法。它会在用“?”替换字符串之前进行转义。这是在DB服务器之前在客户端库中完成的。

如果你有MySQL运行,打开SQL日志,并尝试一些参数化查询,你会看到MySQL服务器正在接收没有“?”的完全替换查询。在其中,但MySQL客户端库已经为您的“参数”中的任何引号转义。

如果您仅使用字符串替换方法B,则“s不会自动转义。

协同地,使用MySQL,您可以提前准备参数化查询,然后稍后重复使用预准备语句。当你准备一个查询时,MySQL解析它并给你一个准备好的语句 - 一些MySQL理解的解析表示。每次使用准备好的语句时,不仅可以防止注入,还可以避免再次解析查询的成本。

而且,如果您真的想要安全,可以修改您的数据库访问/ ORM层,以便1)Web服务器代码只能使用预准备语句,2)您只能在Web服务器启动之前准备语句。然后,即使您的Web应用程序被黑客入侵(例如通过缓冲区溢出漏洞利用),黑客仍然只能使用准备好的语句,但仅此而已。为此,您需要监控您的Web应用程序,并且只允许通过您的数据库访问/ ORM层访问数据库。

答案 3 :(得分:0)

当您通过SQL Server提交查询时,它首先检查过程缓存。如果它发现某些查询完全相等,那么他将使用相同的计划,而不是重新编译查询,只是替换占位符(变量)但在服务器(db)端。

检查系统表master.dbo.syscacheobjects,并进行一些测试,以便您对此主题有所了解。

答案 4 :(得分:0)

这里只是一个警告。 这个 ?语法将正常工作并正确地转义字符串中嵌入的单引号或双引号。

但是我找到了一个不起作用的案例。我有一个列跟踪“n.n.n”形式的版本字符串,例如“1.2.3”格式似乎导致错误,因为它看起来像一个真实的数字直到第二个“。”。例如:

   rec = (some_value, '1.2.3')
   sql = ''' UPDATE some_table
              SET some_column=?
              WHERE version=? '''
    cur = self.conn.cursor()
    cur.execute(sql, rec)

失败并显示错误“提供的绑定数量不正确。当前语句使用1,并且提供了2个。”

这样可行:

   vers = '1.2.3'
   rec = (some_value)
   sql = ''' UPDATE some_table
              SET some_column=?
              WHERE version='%s' ''' % (vers)
    cur = self.conn.cursor()
    cur.execute(sql, rec)