PHP使用了PDO还可能存在sql注入的情况
本文作者:hl0rey
“用 PDO 来防止 SQL 注入。”大概学过 PHP 的都听说过这句话。代码中出现了 PDO 就行了吗?答案肯定是否定的。接下来给大家介绍几种使用了 PDO 还是不能防止 sql 注入的情况。
第一种情况
正如晏子霜前辈所言:
对于做代码审计来说,遇到 Pdo 预编译,基本上就可以对注入说再见了,我们有理由相信,一个网站,基本上全站都使用了 Pdo 预编译的情况下,是不可能在一些重要功能点使用拼接的方式进行 SQL 语句的执行,所以说这种漏洞应该是作者故意留的吧。 --某前辈所言
Pdo 直接使用 query 或者 exec 来执行 sql 语句时,不经过预编译,直接执行,所以没有起到防注入的作用。
1、用 query 的情况:
<?phpif (!isset($_GET['id'])){ die();}$dbh=new PDO('mysql:host=localhost;dbname=test_data','root','');$sql="SELECT * FROM `users` WHERE `id`=".$_GET['id'].";";$result=$dbh->query($sql);foreach ($result->fetch(PDO::FETCH_ASSOC) as $item) { echo $item;}foreach ($dbh->errorInfo() as $row){ echo $row;}
2、用 exec 的情况:
<?phpif (!isset($_GET['id'])){ die();}$dbh=new PDO('mysql:host=localhost;dbname=test_data','root','');$sql="SELECT * FROM `users` WHERE `id`=".$_GET['id'].";";$result=$dbh->exec($sql);echo $result;
第二种情况
在 sql 语句预编译之前,修改了 sql 语句。
PDO 预编译,预先编译一下,php 会把 sql 语句先放到数据库去执行一下。
所以说,就算污染了 sql 语句,导致在预编译之后,无法传入变量,执行语句也没关系.因为在预编译之时,sql 语句已经被执行了。
测试这几个例子要监控 sql 语句的执行。
在mysql命令行或者客户端管理工具中执行:SHOW VARIABLES LIKE "general_log%";结果:MariaDB [(none)]> SHOW VARIABLES LIKE "general_log%";+------------------+----------+| Variable_name | Value |+------------------+----------+| general_log | OFF || general_log_file | kali.log |+------------------+----------+2 rows in set (0.00 sec)OFF说明没有开启日志记录分别执行开启日志以及日志路径和日志文件名SET GLOBAL general_log = 'ON';默认日志文件位置 /var/lib/mysql/kali.log 还要注意这时执行的所有sql都会别记录下来,方便查看,但是如果重启mysql就会停止记录需要重新设置然后执行 watch tail /var/lib/mysql/kali.log
情况复杂的多,举三个例子。
1、预编译的变量名可以修改
<?phpif (!isset($_GET['id'])){ die();}$dbh=new PDO('mysql:host=localhost;dbname=test_data','root','');$sql="SELECT * FROM `users` WHERE `id`=:".$_GET['id'];$sth=$dbh->prepare($sql);$sth->execute(array(":id"=>1));$result=$sth->fetch(PDO::FETCH_ASSOC);foreach ($result as $item){ echo $item;}
2、能拼接语句,在预编译之前,污染语句
<?phpif (!isset($_GET['id'])){ die();}$dbh=new PDO('mysql:host=localhost;dbname=test_data','root','');$sql="SELECT * FROM `users` WHERE `id`=:id ".$_GET['id'];$sth=$dbh->prepare($sql);$sth->execute(array(":id"=>1));$result=$sth->fetch(PDO::FETCH_ASSOC);foreach ($result as $item){ echo $item;}
3、第一个例子和第二个例子都可以 sqlmap 一把梭解决。但是下面这种情况是无法 sqlmap 一把梭的。
<?phpif (!isset($_GET['id'])){ die();}$dbh=new PDO('mysql:host=localhost;dbname=test_data','root','');$sql="SELECT * FROM `".$_GET['id']."` WHERE `username`=:name";$sth=$dbh->prepare($sql);$sth->execute(array(":name"=>'admin'));$result=$sth->fetch(PDO::FETCH_ASSOC);foreach ($result as $item){ echo $item;}
第三种情况
PHP Pdo 本地模拟 sql 预编译,可能存在宽字节注入。
我们需要抓包来看 php 本地模拟预编译的通信过程,但是 windows 不能在本地回环网卡上监听流量,所以我们要在虚拟机里装一个 mysql,然后在虚拟机里抓包看看。这里我用的是 kali 虚拟机。
1、首先把修改 mysql 的配置文件,kali 下的配置文件的位置是
/etc/mysql/my.cnf
2、打开它之后可以发现,它包含了两个文件夹,我们只需要修改 mysql 的监听地址就好了,暂不关注其他。
3、找到它的服务端配置文件
4、把监听地址简单粗暴的修改为
0.0.0.0
5、然后自己创建些测试数据就好了。
测试代码
<?phptry { $dbh = new PDO('mysql:host=192.168.200.134;dbname=test_data', "hl0rey", "hl0rey"); //$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbh->query("set names gbk"); $name=$_REQUEST['name']; $sql="SELECT * FROM `user` WHERE `username`=:username"; $sth=$dbh->prepare($sql); $sth->execute(array(":username"=>$name)); $rst=$sth->fetchAll(); foreach ($rst as $row){ echo $row['id']."<br>"; echo $row['username']."<br>"; echo $row['password']; } $dbh = null;} catch (PDOException $e) { print "Error!: " . $e->getMessage() . "<br/>"; die();}?>
测试过程
1、在数据库所在的虚拟机打开 wireshark,设置过滤条件为 mysql
2、正常执行一下,搜索下 username 为 hl0rey 的用户
3、然后来看抓包的情况,可以看到其中有两个查询请求。
第一个查询请求是设置与 mysql 服务端通信的编码,也就是
set names gbk
第二个查询请求则是我们查询名为 hl0rey 用户的查询请求
4、我们输入一个单引号后,再进行查询。预料之中,一片空白。
5、看一下抓到的数据包,还是抓到了两个查询请求。
我们直接看第二个。php 仅仅是在单引号之前加入了反斜杠进行转义就提交到了 MySQL 中执行。所以并没有查到该用户。
到此,我们就知道,PHP 本地模拟转义,类似是将用户输入变量进行了一次 mysqli_real_escape_string 过滤。
6、我们在单引号之前加一个
%df
,再次进行查询。仍然是没有回显。
我们来看抓到的包,除了两个查询请求之外,还有一个错误。
我们先看这个错误。
因为多出来一个单引号,所以导致语句报错。
再看第二个查询请求里的 sql 语句。
手工进一步测试,输入
%df' or 1 --
,直接返回了数据库所有的信息。
可以确认存在 sql 注入。
总结
1、避免这样的问题的办法就是让 php 不要进行本地模拟预编译。将代码中第四行的注释去掉之后,php 就尽量的不进行本地模拟预编译了。
2、经过测试,PHP 全版本都存在这样的问题(默认配置)。只要是本地模拟 sql 预编译都会有这样的问题,值得一提的是,php5.2.17 即使将本地模拟预编译的参数设置为 false,还是会存在宽字节注入,也就是说,它仍然是用模拟预编译,我猜测是 php 的版本太低,mysql 的版本太高的缘故吧。如果有知道真实原因的,希望能指点我一下。
- 第一种情况
- 第二种情况
- 第三种情况
- 总结