Sql 带绑定参数的预处理语句优于带转义/引用参数的插值语句的原因

Sql 带绑定参数的预处理语句优于带转义/引用参数的插值语句的原因,sql,sql-injection,Sql,Sql Injection,为了防止SQL注入,建议使用带有绑定值的预处理语句。这确保了数据库能够区分SQL中的实际逻辑(必须解析、解释和优化)和数据(不需要解释),因此不会解释和执行数据中的命令 实现某种保护的另一种方法是使用转义库,它解除数据中的重要字符,以便它们不会被解释 在我看来,一般来说,建议使用带有绑定参数的准备语句,而不是转义输入。例如,带有绑定值的准备语句在循环中确实有一些性能优势 我的问题:是否有任何安全原因让我们更喜欢带有绑定值的语句而不是转义语句?如果是,具体原因是什么 我可能想到的一个原因是“转义很

为了防止SQL注入,建议使用带有绑定值的预处理语句。这确保了数据库能够区分SQL中的实际逻辑(必须解析、解释和优化)和数据(不需要解释),因此不会解释和执行数据中的命令

实现某种保护的另一种方法是使用转义库,它解除数据中的重要字符,以便它们不会被解释

在我看来,一般来说,建议使用带有绑定参数的准备语句,而不是转义输入。例如,带有绑定值的准备语句在循环中确实有一些性能优势

我的问题:是否有任何安全原因让我们更喜欢带有绑定值的语句而不是转义语句?如果是,具体原因是什么


我可能想到的一个原因是“转义很棘手”,转义库需要与数据库功能完全匹配。。。还有什么吗?

一个原因是转义仅用于保护带引号的字符串文本。例如(我将使用伪代码,因为您没有引用任何特定的编程语言):

在上面的示例中,撇号应该转义,因此它将成为
WHERE name='O'Reilly'
,因此可以安全地插入SQL查询,而不会导致任何错误

但是,数字不需要在SQL中引用,对包含撇号的字符串进行转义将不会起到正确的作用:

$escapedId = EscapeString("123'456")

$sql = "SELECT * FROM MyTable WHERE id = $escapedId"
这将导致
其中id=123\456
,这仍然是一个错误

您可能会说,“我们将数字放在单引号中”,但这并不总是可能的,例如MySQL中的
LIMIT
子句需要实整数,而不是包含数字的带引号的字符串

除了上述问题,使用参数编写代码比使用转义更容易

例如,您可以编写如下代码:

$sql = "INSERT INTO mytable (col1, col2, col3, col4, col5, col6) 
  VALUES ('" . mysqli_real_escape_string($_POST['col1']) . "', " 
  . $mysqli->real_escape_string($_POST['col2']) . "', '" 
  . $mysqli->real_escape_string($_POST['col3']) . "', '" 
  . $mysqli->real_escape_string($_POST['col4']) . ", '" 
  . $mysqli->real_escape_string($_POST['col5']) . "', '" 
  . $mysqli->real_escape_string($_POST['col6']) . "')";
你能找出错误吗?有足够的时间,我相信你能做到。但是它会减慢你的编码速度,并且在你寻找缺少的引号字符和其他错误时会让你眼睛疲劳

但写这篇文章要容易得多,之后也更容易阅读:

$sql = "INSERT INTO mytable (col1, col2, col3, col4, col5, col6) 
  VALUES (?, ?, ?, ?, ?, ?)";

查询参数对于更多的数据类型是安全的,它们可以帮助您更快地编写代码,并且错误更少。这是一个巨大的胜利。

整个问题的陈述都来自一个古老的严重错觉

解除数据中重要字符的转义

坦白说,这是胡说八道

  • 没有包罗万象的“重要人物”。如果一个字符被切断,可能会对一个查询部分产生毁灭性的影响,那么它可能像另一个查询部分中的羔羊一样无害。反之亦然
  • 没有抽象的包罗万象的“数据”。所有查询部分都是不同的,但转义只适用于一个部分
  • 而且根本没有“用逃跑来保护”这样的做法
转义用于转义SQL字符串中的特殊字符。而且从来没有打算提供任何保护。这只是一项被严重误解和误会的技术措施。这就像声称我们在程序中遵循正确的语法只是为了保护。我们遵循正确的语法,使解释器/编译器理解我们的代码。彼此彼此。转义用于生成语法正确的SQL字符串。这当然是注射的副作用证明。但再一次,逃跑的任务不是保护

这里出现了转义问题#1:字符串不是查询中使用的唯一数据类型。在任何其他数据文本上使用字符串转义都是一条通向灾难的捷径

此外,即使对于字符串,转义也是一种本质上可分离的措施,它本身就构成了一整罐蠕虫,使您的代码容易出现各种人为错误,并构成转义问题#2:

引用我关于此事的文章:

如您所见,格式化数据库的值实际上分为两部分:转义变量和在查询中引用值。这就是所有奇迹发生的地方,是SQL注入的无数实际案例的原因所在

在您的简化示例中,所有代码都绑定在一起,因此很难忽略正确的例程。但在现实生活中,代码要复杂得多,由大型不同的模块组成。转义在一个模块中完成,而在另一个模块中引用。或者不是。实际上没人能说出来。我相信这个值已经被转义了。或者我将对其进行转义以确定,并在数据中引入额外的转义字符。或者我是一个新开发人员,不理解您在这里发布的示例,我正在观看一个youtube视频,视频中说,转义阻止SQL注入。我知道这个值已经被转义了,所以我可以安全地输入查询。既然它是一个整数,我为什么要浪费引号呢

或者我知道数据在进入应用程序时已经转义了,所以我以后不必在某些内部操作期间转义它(例如,移动到另一个表时)。结果是一级二阶SQL注入

相信我,我在野外见过所有这些病例。这样一个单独的格式将导致一片混乱,并浪费了注射的机会


与转义不同,准备好的语句总是确保正确处理查询部分。

虽然我没有经验证据证明使用了它,但也许值得指出的是,使用混合使用参数变量和常量值的准备语句
$sql = "INSERT INTO mytable (col1, col2, col3, col4, col5, col6) 
  VALUES (?, ?, ?, ?, ?, ?)";
--we could infer that name will vary and type will not
--but we'd have to analyze all queries sent to work this out
SELECT * FROM person WHERE type = 1 AND name = 'john'
SELECT * FROM person WHERE type = 1 AND name = 'mark'
SELECT * FROM person WHERE type = 1 AND name = 'luke'


--we can easily say that type will vary and name will too
--the previously seen queries would infer differently
SELECT * FROM person WHERE type = @t AND name = @n
sayHello(string name){
  console.print("hello " + name);
}

var name = console.readString(),
sayHello(name);
main(){
  disk.write("sayHello(string name){console.print(\"hello \"" + name +");}", "c:\\temp\\new.lang");
  launchExe("langcompiler.exe", "c:\\temp\\new.lang");
  launchExe("c:\\temp\\new.exe");
}