前言

有个问题一直困惑着我,预处理的原理是什么,使用预处理就绝对安全吗,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'] . '&emsp;&emsp;';
    echo $row['name'] . '&emsp;&emsp;';
    echo $row['passwd'] . '&emsp;&emsp;';
    echo $row['text'] . '&emsp;&emsp;';
    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%231%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'] . '&emsp;&emsp;';
    echo $row['name'] . '&emsp;&emsp;';
    echo $row['passwd'] . '&emsp;&emsp;';
    echo $row['text'] . '&emsp;&emsp;';
    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'] . '&emsp;&emsp;';
    echo $row['name'] . '&emsp;&emsp;';
    echo $row['passwd'] . '&emsp;&emsp;';
    echo $row['text'] . '&emsp;&emsp;';
    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注入 最终表现的方式,但中间一些过程并没有深刻的认识、理解,想蹲个师傅给我讲解
在这里插入图片描述
一点点小感触:安全并不是靠某一现成的工具就能达到的,还要人的安全意识,和对工具的正确使用

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐