PDO预处理是如何防止SQL注入的
通过日志分析PDO是如何防止SQL注入的
前言
有个问题一直困惑着我,预处理的原理是什么,使用预处理就绝对安全吗,pdo 的 prepare 对我们的 sql 语句到底做了什么
一、模拟预处理
对于一个只知道 pdo 预处理,但是不清楚其安全性的人(本人没错了),写出的代码应该是这样子的
<?php
$host = '127.0.0.1';
$username = 'root';
$password = 'root';
$database = 'user';
$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);
$stmt = $conn->prepare('select * from user where id = ?');
$stmt->execute([$_GET['id']]);
foreach ($stmt as $row) {
echo $row['id'] . '  ';
echo $row['name'] . '  ';
echo $row['passwd'] . '  ';
echo $row['text'] . '  ';
echo '<br />';
}
这样写就绝对安全了吗,我们打开 mysql 日志,看看它到底执行了什么
set global general_log = 'on' # 打开日志功能
show variables like 'general%' # 查看日志是否开启,和日志保存的路径
输入 id=1'%23
验证 SQL注入,发现最终执行的 SQL语句 如下
这。。。看着似曾相识的感觉,对了,不就是 addslashes()
吗,突然觉得无法理解,被吹上天的 PDO 预处理 就这??(别喷我,小菜鸡当时的真实感受)
这不是可以宽字节注入吗,说干就干,在第六行下插入如下代码
$conn->query('set names gbk');
这个从日志上不好体现,只能从结果上看
在插入上述代码前,1%df' or 1=1%23
和 1%df' or 1=2%23
,都会返回结果,应该是,%df 不能被正确解码,而丢弃了,之所以不认为是 被 intval
了,是因为 1%0a
并不能返回结果
但是插入了上述代码之后,结果就不同了
id=1%df' or 1=1%23 # 返回所有结果
id=1%df' or 1=2%23 # 不返回结果
所以即使正确的使用 PDO 预处理也是有存在 SQL注入 的风险的
所以说,PDO 预处理不过如此,本文完
好吧,开个玩笑,精彩继续
二、不正确的使用 PDO 预处理
如果是刚刚是普通青年使用 PDO预处理,那么接下来就是 二逼青年 的使用方式
关门,放代码
<?php
$host = '127.0.0.1';
$username = 'root';
$password = 'root';
$database = 'user';
$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);
$stmt = $conn->prepare("select * from user where id = '{$_GET['id']}'");
$stmt->execute();
$stmt->debugDumpParams();
echo '<br /><br />';
foreach ($stmt as $row) {
echo $row['id'] . '  ';
echo $row['name'] . '  ';
echo $row['passwd'] . '  ';
echo $row['text'] . '  ';
echo '<br />';
}
payload:id=1'%23
日志内容如下,发现与普通的 字符型SQL注入 并无区别
因为参数在 prepare
之前就拼接到了 SQL语句中,并没有正确完成预处理,不再深入
三、本地预处理
因为不相信预处理真就这么水,所以我又各种百度,最后发现 PDO 有三个重要的参数都是默认为 true 的,会存在安全隐患
PDO::ATTR_EMULATE_PREPARES // 启用或禁用预处理语句的模拟
PDO::ATTR_ERRMODE // 错误报告
PDO::MYSQL_ATTR_MULTI_STATEMENTS // 多语句查询
后两个没细究,大概是会造成报错注入和堆叠注入
百度说,模拟预处理并不安全,使用本地预处理才是安全的,但是为什么要默认使用模拟预处理呢,查看 php 手册,明明都说了,本地预处理失败会使用模拟预处理。
PDO::ATTR_EMULATE_PREPARES 启用或禁用预处理语句的模拟。有些驱动不支持或有限度地支持本地预处理。使用此设置强制PDO总是模拟预处理语句(如果为 true ),或试着使用本地预处理语句(如果为 false)。如果驱动不能成功预处理当前查询,它将总是回到模拟预处理语句上。 需要 bool 类型。
修改代码如下
<?php
$host = '127.0.0.1';
$username = 'root';
$password = 'root';
$database = 'user';
$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$conn->query('set names gbk');
$stmt = $conn->prepare('select * from user where id = ?');
$stmt->execute([$_GET['id']]);
$stmt->debugDumpParams();
echo '<br /><br />';
foreach ($stmt as $row) {
echo $row['id'] . '  ';
echo $row['name'] . '  ';
echo $row['passwd'] . '  ';
echo $row['text'] . '  ';
echo '<br />';
}
添加了一行代码 $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
使 PDO 使用 本地预处理
继续使用之前宽字节注入的 payload id=1%df' or 1=1%23
,发现已不能正确返回结果,查看日志如下
直呼原来如此,困惑我多年的疑惑竟然让我以一种 情理之中,意料之外 的感觉解决了,大概就是觉得自己应该想得到,但偏偏没有想到的那种感觉
16进制解码为
1DF' or 1=1# //因为 DF 是我输入的 %df,所以不参与解码
正是输入的数据
总结
预处理好像还有个机制,执行出错的语句貌似并不会出现在日志中。花了一晚自习的时间,只是大概了解了预处理防止 SQL注入 最终表现的方式,但中间一些过程并没有深刻的认识、理解,想蹲个师傅给我讲解
一点点小感触:安全并不是靠某一现成的工具就能达到的,还要人的安全意识,和对工具的正确使用
更多推荐
所有评论(0)