And*_*nes 9 php mysql mysqli sql-injection prepared-statement
我看到经常重复的评论"总是使用准备好的查询来防止SQL注入攻击".
使用准备好的查询和构造的查询之间的实际区别是什么,用户输入总是被清理?
建造
function quote($value) {
global $db;
return "'" . mysqli_real_escape_string($db, $value) . "'";
}
$sql = "INSERT INTO foo (a, b) VALUES (" . quote($a) . "," . quote($b) . ")";
Run Code Online (Sandbox Code Playgroud)
准备
$stmt = mysqli_prepare($db, "INSERT INTO foo (a, b) VALUES (?, ?)");
mysqli_stmt_bind_param($stmt, "ss", $a, $b);
Run Code Online (Sandbox Code Playgroud)
除了冗长和风格之外,我还有什么理由想要使用另一个?
一个很好的问题.
逃离
逃避是更传统的做事方式.它会向任何不安全的数据添加反斜杠,因此如果有人试图传入直接的SQL语句,它将无害地传递而不是按字面意思传递.这里的问题是,有一些边缘情况,攻击者仍然可以绕过逃逸功能.其中绝大多数都涉及到诸如改变字符集之类的神秘技巧.要理解的是,如果你追求逃避,最重要的是在所有情况下都不确定安全性.
准备好的陈述
准备好的陈述不会受到相同的缺陷,因为您将查询分为两部分.第一部分包含查询,第二部分包含该查询的参数.因此,MySQL可以以一种不允许注入的安全方式区别对待参数(至少没有人找到方法).
如果需要使用不同的值反复运行相同的查询,那么准备好的语句可以节省大量时间.MySQL将语句存储在内存中,因此多次执行非常快.
但是,您无法参数化查询的某些部分(即表名).
买者自负
如此准备好的陈述是100%安全的,对吗?为什么不直接使用准备好的陈述?我们已经解决了SQL注入FOREVER!嗯,不太好.首先需要了解一些警告.
第一个是具体的mysqli
.您将希望安装MySQL Native Driver(mysqlnd
)来执行返回结果的预准备语句mysqli
.具体来说,mysqli_stmt_get_result,它返回一个结果集mysqli_query
.某些共享主机和较旧的服务器仍在使用较旧的MySQL客户端库.如果您想知道自己在使用哪个,请运行phpinfo()
并查找该mysqlnd
块.mysqlnd
即使您没有使用预准备语句也是一个好主意,因为它是由PHP团队专门为了充分利用MySQL而构建的.但是,有些人无法更改驱动程序,因为这是服务器配置级别的更改.
第二种是针对那些使用PDO的人(另一种使用PHP连接到数据库的方法).现在,PDO有一种更好的处理预处理语句的方法(它有一个方便的别名系统),并且不需要mysqlnd
做预备语句.这里需要注意的是,默认情况下,PDO只模拟准备好的语句,这意味着如果你没有正确配置它,你所做的只是使用一个库来为你进行转义.有关详细信息,请参阅此页面下方PDO::ATTR_EMULATE_PREPARES
.
第三个是迄今为止最重要的一个,你需要在这里密切关注,因为如果你不小心,这会对你的程序产生影响.大多数提倡准备语句的人倾向于忽略这里最大的陷阱,即你在数据库上做了更多的查询(这里是关于如何通过CLI完成它们的MySQL 5.5手册).现在,for INSERT
和UPDATE
将要一遍又一遍地运行相同查询的语句没有比较.准备好的语句可以为你的连续对应物节省很多时间.但是SELECT
,因为它变得不那么清楚了.以上面的例子为例.它是通过自动编号字段简单地提取记录.但现在需要运行2个查询,而不是一个.如果这是一个流量较低的内部应用程序可能不是什么大问题,但如果该页面每天获得100,000个查看,那么您可能希望降低该页面上的开销.如果是这样,建立一个直接查询可能是一个解决方案.
所以,简而言之
mysqli_real_escape_string
有一些陷阱,但只要你了解这些陷阱并且不将它视为你和SQL注入之间的唯一安全性,就可以使单个查询"足够安全".
mysqli_prepare
始终是安全的,但也会进行更多查询,因此对您的数据库和/或程序来说可能效率不高.一些问题应该总是准备好,而有些问题可能不应该.
其他解决方案也可以使用,而不是使用任何一种解
最后,由您来决定每个适合的位置以及使用另一个更好的位置.
准备好的查询与参数分开发送到 SQL 服务器,这意味着如果执行多次(例如使用不同的参数),它们只需要编译/优化一次。对于大型数据集来说,这可能非常重要。
除此之外,只要在使用未准备的查询时在所有情况下输入实际上都正确转义,它们在功能上是相同的。
也就是说,准备好的查询不太容易出现疏忽,通常会带来更好的可维护性。
编辑:另请查看 @eggyal 的评论,了解为什么准备好的语句始终是注入安全的,而不是转义的查询参数。