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 的版本太高的缘故吧。如果有知道真实原因的,希望能指点我一下。

原文发布于微信公众号 - 信安之路(xazlsec)

原文发表时间:2018-05-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT 指南者专栏

【SSH框架】之Hibernate系列(一)

微信公众号:compassblog 欢迎关注、转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1、Hibernate框架概述 (1)、什么是Hiber...

2778
来自专栏我的博客

TP入门第三天

1、系统常量 TP2.1版本:(蓝色是3.0中去掉) __ROOT__  : 网站根目录地址  __APP__  : 当前项目(入口文件)地址  __GROUP...

2835
来自专栏别先生

Oracle的登陆问题和初级学习增删改查(省略安装和卸载)

1:学习Oracle首先需要安装Oracle,网上已经有很多很多教程了,这里不做叙述,自己百度即可,这里安装的标准版,个人根据需求安装学习或者企业开发即可。如果...

1976
来自专栏大数据和云计算技术

MonetDB学习笔记

这个文章是我2013年6月写在博客里面的,翻出来挺有意思,MonetDB有很多技术值得学习。 1 架构: 三层软件架构: SQL front-end:前端SQL...

59911
来自专栏杨建荣的学习笔记

关于pl/sql的代码保护(r3笔记28天)

在开始学习数据库的时候,总是尝试手动创建数据库,安装完成之后需要运行一些脚本,总是看到屏幕上闪个不停,可以看到大多数的存储过程代码都是一堆乱码,最开始还以为是乱...

3107
来自专栏Java成神之路

PL/SQL学习笔记_02_游标

        在 PL/SQL 程序中,对于处理多行记录的事务经常使用游标来实现。 

1104
来自专栏JackeyGao的博客

Django + MySQL 查询不区分大小写问题

由于测试环境的 sqlite 没有问题, 所以怀疑在 mysql 的配置上面。 原来是字符集校对规则的问题, utf8_general_ci 不区分大小写, 可...

1562
来自专栏GreenLeaves

oracle 表空间tablespace

一、Oracle 表空间的组成 Everoone knows Oracle数据库真正存放数据的是数据文件,Oracle表空间是逻辑上的概念,他在物理上是并不存在...

3298
来自专栏拂晓风起

“class”类型重定义 || 防止头文件重复加载 || 两个类之间互相引用

1704
来自专栏Linux驱动

C-fopen,fwrite,fread,fseek笔记

FILE * fopen(const char * path,const char * mode);

2272

扫码关注云+社区

领取腾讯云代金券