PDO下的sql注入
一直以为PDO预处理后就无法注入了,直到看到了这篇文章
PDO的预处理分为模拟预处理和非模拟预处理
因为不是所有数据库驱动都支持SQL预编译,所以PDO存在“模拟预处理机制”。如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()
的时候才发送给数据库执行;
非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。
PDO默认为模拟预处理,如果设置了PDO::ATTR_EMULATE_PREPARES => false
,那么PDO不会模拟预处理。
那么预处理下是怎么实现注入的呢
模拟预处理
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "test";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
//设置 PDO 错误模式,用于抛出异常
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 关闭模拟预处理
// $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
catch(PDOException $e)
{
echo $e->getMessage();
}
$name = $_GET['name'];
$sql = "select sno,".$_GET['id']." from students where sname = ?";
$stmt = $conn->prepare($sql);
$stmt->bindParam(1,$name);
$stmt->execute();
while($row=$stmt->fetch(PDO::FETCH_ASSOC))
{
var_dump($row);
echo "<br>";
}
此sql语句的id可控构造一下可执行多条语句
?id=sdept from students;create table pdotest(id int(10));%23
成功创建pdotest表
如果还设置了setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
的话,可以报错注入
?id=sname,spwd from students where (1 and extractvalue(1,concat(0x7e,(select(database())),0x7e)));%23
当非模拟预处理时
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "test";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
//设置 PDO 错误模式,用于抛出异常
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 关闭模拟预处理
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
catch(PDOException $e)
{
echo $e->getMessage();
}
$name = $_GET['name'];
$sql = "select sno,".$_GET['id']." from students where sname = ?";
$stmt = $conn->prepare($sql);
$stmt->bindParam(1,$name);
$stmt->execute();
while($row=$stmt->fetch(PDO::FETCH_ASSOC))
{
var_dump($row);
echo "<br>";
}
此时无法执行多条语句,但报错注入依旧可以,因为此时MySQL服务端prepare时报错,然后通过设置PDO::ATTR_ERRMODE将MySQL错误信息打印
总结:
主要还是sql语句模板使用变量动态拼接造成的注入
使用PDO时尽量用非模拟预处理,同时禁止多语句查询
参考