使用绑定参数编制报表的原因超过使用转义/引用参数插入报表的原因



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

另一种实现某些保护的方法是使用转义库,该库会解除数据中的重要字符的武装,这样它们就不会被解释。

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

我的问题:是否有任何安全理由更喜欢使用绑定值准备语句而不是转义?如果是,具体原因是什么?

我可能会想到的一个原因是"转义很棘手",并且转义库需要与数据库功能完全匹配。。。还有别的吗?

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

$escapedName = EscapeString("O'Reilly")
$sql = "SELECT * FROM MyTable WHERE name = '$escapedName'"

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

然而,SQL中不需要引用数字,并且转义包含撇号的字符串不会起到正确的作用:

$escapedId = EscapeString("123'456")
$sql = "SELECT * FROM MyTable WHERE id = $escapedId"

这将导致WHERE 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字符串。这当然是防注射的副作用。但再说一遍,逃跑的任务绝不是保护。

这里出现了Escapeng问题#1:字符串不是查询中使用的唯一数据类型。而在任何其他数据文字上使用字符串转义是通往灾难的直接道路。

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

引用我关于此事的文章,如果逃跑是安全的,我为什么要使用事先准备好的陈述?:

正如您所看到的,格式化数据库的值被有效地分为两部分,转义变量和引用查询中的值。这就是所有魔法发生的地方是导致无数SQL注入的真实案例的原因。

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

或者我知道数据在进入应用程序时已经被转义了,所以在以后的一些内部操作中(例如,当移动到另一个表中时)我不必对其进行转义。并因此进行了一级二阶SQL注入。

相信我,我在野外见过这么多案例。这种分离的格式带来了一个混乱和浪费的注入机会。

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

虽然我没有经验证据可以证明它被使用了,但也许值得指出的是,使用混合使用参数变量和常数值的准备语句可以让数据库查询优化器知道应用程序会改变查询的哪些部分,哪些部分是常量。这可以用于查询规划。如果你进入了一种在查询中引用所有数据的模式,那么优化器就无法猜测哪些部分可能很好,哪些部分是固定的,而不保留查询的所有变体的历史记录,并查看差异来找出哪些部分是不同的。

--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

我提到过,我从来没有读过任何表明它被使用的东西,但人们可以读到sql server做出的名为参数嗅探的决定,即它根据加载到参数中的第一组值缓存和重用计划,这可能不会在所有情况下生成最佳查询

当然;这可能会让你下定决心放弃参数,或者使用提示让数据库每次都重新计划查询,但最好与服务器一起工作,而不是与服务器对抗,并使用技术使其根据常见或最佳的值进行计划


即使我们不喜欢根据我们对变量和常量的了解来调整计划,使用一个准备好的语句至少应该允许数据库进行编译,然后重用编译工作,而不是重做,从而减少为运行语句做准备所需的资源量。

用前端语言术语思考您的提案:

要更改变量操作的值,您可以在内存中通过简单的值分配来更改运行时值:

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");
}

自我修改程序并重新编译只是为了更改函数调用中使用的值,这是荒谬的,对吧?

除了数据库服务器对它接收到的每一个未参数化的SQL都是这样做的之外,除非它付出一些努力来确定它刚刚得到的查询是否与X分钟前得到的查询基本相同,除了一些数据部分,提取这些数据,并将其插入5分钟前的编译工作中。。

最新更新