PDO可以被认作是一种通过编译SQL语句模板来运行SQL语句的机制。
- 查询仅需解析(或预处理)一次,但可以用相同或不同的参数执行多次。当查询准备好后,数据库将分析、编译和优化执行该查询的计划。对于复杂的查询,此过程要花费较长的时间,如果需要以不同参数多次重复相同的查询,那么该过程将大大降低应用程序的速度。通过使用预处理语句,可以避免重复分析/编译/优化周期。简言之,预处理语句占用更少的资源,因而运行得更快。
- 提供给预处理语句的参数不需要用引号括起来,驱动程序会自动处理。如果应用程序只使用预处理语句,可以确保不会发生SQL注入。(然而,如果查询的其他部分是由未转义的输入来构建,则仍存在SQL注入的风险)。
示例1,如下为一个简易的PDO预处理查询环境
<link.php>
<?php
$servername="localhost";
$username="root";
$password="root";
$database="test";
$link =new PDO("mysql:host=$servername;dbname=$database",$username,$password);
$stmt=$link->prepare('select * from users where id=?');
$stmt->execute([$_GET['id']]);
foreach ($stmt as $item) {
echo $item['id'].' ';
echo $item['name'].' ';
echo $item['sex'].' ';
echo $item['passwd'].' ';
}
通过一个简易html页面提交id参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>pdo-test</title>
</head>
<body>
<form action="link.php" method="get">
<input name="id" type="text">
<input name="submit" type="submit">
</form>
</body>
</html>
测试数据表users
提交敏感字符进行查询并查看日志记录,发现对 ' 和 " 进行了转义处理,这就有点类似于addslashes()和mysql_real_escape_string()
那可能会想到是否可以进行宽字节注入,使用payload:%df ' or 1=1#一试便知
答案是当然不可以。宽字节注入需要满足两个条件:1、数据库连接使用宽字符集。2、使用转义函数来过滤输入的敏感字符。
这就涉及到PDO的转义机制:
- 本地转义,使用单字节字符集(PHP<5.3.6)来对输入进行转义,但这种方式存在安全隐患。在PHP版本小于5.3.6时,本地转义只能转换单字节的字符集,大于5.3.6的版本会根据PDO连接中指定的字符集进行转义。
- mysql服务端转义,首先将SQL语句模板发送到mysql server,而后再讲绑定的变量发送给mysql server,这里的转义在mysql server中完成,根据PDO连接中指定的字符集进行转义。这样的转义方式更健全,同时还可以在有多次重复查询的业务场景下,通过复用模板来提高程序的性能。如果使用此转义机制需要添加参数:$PDO->setAttribute(PDO::ATTR_EMULATE_PREPARES,false),此参数默认情况下为true。如果不修改该参数,PDO会将输入的参数使用本地转义后和sql语句模板拼接并发送至mysql server。
示例1为本地转义机制,示例2如下添加PDO::ATTR_EMULATE_PREPARES参数后看看效果:
<?php
$servername="localhost";
$username="root";
$password="root";
$database="test";
$link =new PDO("mysql:host=$servername;dbname=$database",$username,$password);
$link->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$stmt=$link->prepare('select * from users where id=?');
$stmt->execute([$_GET['id']]);
foreach ($stmt as $item) {
echo $item['id'].' ';
echo $item['name'].' ';
echo $item['sex'].' ';
echo $item['passwd'].' ';
}
执行刚才的payload后查看日志,比刚才多出了一条预处理语句,原因是数据库先对预处理语句模板进行了解析,再将绑定参数发送给服务端执行。
本文只做参考,如有不当之处还请师傅们指正,要想更加深入理解PDO还请查阅PHP官方文档。