专栏首页贝塔安全实验室PHP PDO & Injection Bypass

PHP PDO & Injection Bypass

本文作者:Twe1ve(贝塔安全实验室-核心成员)

PDO:PHP 数据对象

  1. PHP访问数据库定义了一个轻量级的一致接口。
  2. PDO 提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据。
  3. 使用预处理和存储过程

PDO连接MySql数据库:

<?php
$dbms='mysql';     //数据库类型
$host='127.0.0.1'; //数据库主机名
$port='3306';      //数据库端口
$dbName='test';    //使用的数据库
$user='root';      //数据库连接用户名
$pass='root';          //对应的密码
$dsn="$dbms:host=$host;port=$port;dbname=$dbName";   
try {     
$dbh = new PDO($dsn, $user, $pass); //初始化一个PDO对象     
echo "连接成功<br/>";
    /*你还可以进行一次搜索操作
    foreach ($dbh->query('SELECT * from FOO') as $row) {
        print_r($row); //你可以用 echo($GLOBAL); 来看到这些值
    }
    */
    $dbh = null;
} catch (PDOException $e) {
    die ("Error!: " . $e->getMessage() . "<br/>");
}
//默认这个不是长连接,如果需要数据库长连接,需要最后加一个参数:array(PDO::ATTR_PERSISTENT => true) 变成这样:
$db = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
?>

PDO多语句安全问题:

使用PDO中query()函数同数据库交互:

<?php$dbms='mysql';
$host='127.0.0.1';
$dbName='test';
$user='root';
$pass='root';
$dsn="$dbms:host=$host;dbname=$dbName";
try {
     $pdo = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
     echo $e;
}
$sql = "select * from user where id=1;";
$sql .= "create table test2 like user";
$stmt = $pdo->query($sql);while($row=$stmt->fetch(PDO::FETCH_ASSOC))
{
    var_dump($row);
    echo "";
}

PDO默认支持多语句查询,如果php版本小于5.5.21或者创建PDO实例时未设置PDO::MYSQL_ATTR_MULTI_STATEMENTS为false时可能会造成堆叠注入

<?php
$dbms='mysql';
$host='127.0.0.1';
$dbName='test';
$user='root';
$pass='root';
$dsn="$dbms:host=$host;dbname=$dbName";
try {
     $pdo = new PDO($dsn, $user, $pass);
} 
catch (PDOException $e) {
     echo $e;
}
$id = $_GET['id'];
$sql = "SELECT * from user where id =".$id;
$stmt = $pdo->query($sql);
while($row=$stmt->fetch(PDO::FETCH_ASSOC)){
    var_dump($row);
    echo "";
}

$id变量可控,以形成堆叠注入

禁止多语句执行:

new PDO($dsn, $user, $pass, array( PDO::MYSQL_ATTR_MULTI_STATEMENTS => false))

PDO预处理安全问题

PDO分为模拟预处理和非模拟预处理。

  1. 模拟预处理是防止某些数据库不支持预处理而设置的,在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。
  2. 非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。

[1]模拟预处理动态拼接形成多语句执行(PHP<5.3.6)

<?php
$dbms='mysql';
$host='127.0.0.1';
$dbName='test';
$user='root';
$pass='root';
$dsn="$dbms:host=$host;dbname=$dbName";
try {
    $pdo = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
    echo $e;
}
//$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$username = $_GET['username'];
$sql = "select id,".$_GET['field']." from user where username = ?";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(1,$username);
$stmt->execute();
while($row=$stmt->fetch(PDO::FETCH_ASSOC))
{
    var_dump($row);
    echo "
";
}

可以看到sql语句field字段可控,这样我们构造field,达到多语句执行的效果。

数据库中语句为:

select id,username from user;create table sss like user;select id from user where username=Z

[2]模拟预处理报错注入(PHP<5.3.6)

上述代码,设置$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //打印报错信息

可以达到报错注入效果

在数据库中语句为:

select id,updatexml(1,concat(user(),0x7e,version()),1) from user;select id from user where username=Z

[3]非模拟预处理报错注入

设置pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //表示是否使用PHP本地模拟prepare:php对sql语句发送采用了prepare--execute方式

此时转义处理交由mysql server来执行,变量和SQL模板是分两次发送的

因此虽然field字段依旧可控,但是多语句不可执行

当设置$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);时,仍然可以进行报错注入

在数据库中语句为:

select id,updatexml(1,concat(user(),0x7e),1) from user where username=Y;

这里可进行报错注入是因为MySQL服务端prepare时报错,然后通过设置PDO::ATTR_ERRMODE将MySQL错误信息打印

在MySQL中执行prepare语句

prepare statm from "select id,updatexml(0x7e,concat(0x7e,user(),0x7e),0x7e) from user where username=?";

使用PDO注意事项:

1.php升级到5.3.6+,生产环境强烈建议升级到php 5.3.9+ php 5.4+

2.php 5.3.8存在致命的hash碰撞漏洞。

3.如果使用了PHP 5.3.6及以前版本,设置PDO::ATTR_EMULATE_PREPARES参数为false(即由MySQL server进行变量处理),php 5.3.6以上版本已经处理了这个问题,无论是使用本地模拟prepare还是调用mysql server的prepare均可。

4.如果使用了PHP 5.3.6及以前版本, 因Yii框架默认并未设置ATTR_EMULATE_PREPARES的值,请在数据库配置文件中指定emulatePrepare的值为false。

注:

1.为什么在DSN中指定了charset, 还需要执行set names <charset>呢?

其实set names <charset>有两个作用:

  1. 告诉mysql server, 客户端(PHP程序)提交给它的编码是什么
  2. 告诉mysql server, 客户端需要的结果的编码是什么

也就是说,如果数据表使用gbk字符集,而PHP程序使用UTF-8编码,我们在执行查询前运行set names utf8, 告诉mysql server正确编码即可,无须在程序中编码转换。这样我们以utf-8编码提交查询到mysql server, 得到的结果也会是utf-8编码。省却了程序中的转换编码问题,不要有疑问,这样做不会产生乱码。

那么在DSN中指定charset的作用是什么? 只是告诉PDO, 本地驱动转义时使用指定的字符集(并不是设定mysql server通信字符集),设置mysql server通信字符集,还得使用set names <charset>指令。

2.PDO::ATTR_EMULATE_PREPARES属性设置为false引发的血案:http://my.oschina.net/u/437615/blog/369481

参考链接:

https://my.oschina.net/zxu/blog/163135

https://www.cnblogs.com/leezhxing/p/5282437.html

https://www.secshi.com/15031.html

本文分享自微信公众号 - 贝塔安全实验室(BetaSecLab),作者:Twe1ve

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-10

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一篇文章带你进入Bypass 技术大门!

    其实我们过这些waf就是个正则的绕过,因为这种通用型的waf,需要考虑到用户体验,他不能出现什么东西就直接拦截,比正则过滤的一些好绕一点,如何成功绕过我们需要具...

    贝塔安全实验室
  • Moriarty Corp靶场攻略

    Moriarty Corp靶场环境包含一台外网服务器和三台内网主机,攻击者需先对外网服务器进行web攻击,依据提交flag后的提示信息,逐步获取内网主机权限。本...

    贝塔安全实验室
  • Windows 系统指令及服务

    ipconfig /all 获取ip地址 所在域 linux:ifconfig -a

    贝塔安全实验室
  • 我是如何找到Donald Daters应用数据库漏洞的

    星期一的晚上像往常一样我通过观看电视节目来打发时间,但并没有什么有趣的节目。于是我决定在手机上寻找乐趣,我开始漫无目的在Twitter上翻看各种推文,一条Fox...

    FB客服
  • 数据库MySQL-设计规范

    a、采用26个英文字母(区分大小写)和0-9的自然数(经常不需要)加上下划线’_'组成; b、命名简洁明确(长度不能超过30个字符); c、例如:user,...

    cwl_java
  • 接口测试 | 21 基于flask弄个restful API服务出来

    概述 上篇我们很简单的分享了如何基于flask搞一个支持http GET\POST\HEAD\DELETE方法的服务,大家可以根据这个简单的实例进行扩展。 下面...

    苦叶子
  • MySQL半连接的攻略式思考

    在此说是攻略式思考,是因为仅供参考,说是攻略,是因为暂时还没有严谨的结论,目前只能说对结论有帮助。

    jeanron100
  • Laravel框架模型的创建及模型对数据操作示例

    本文实例讲述了Laravel框架模型的创建及模型对数据操作。分享给大家供大家参考,具体如下:

    砸漏
  • RHEL 8使用的内核版本

        已知RHEL/CentOS内核使用情况如下,详细可见版本信息可见:CentOS(RHEL)内核版本与系统版本对应关系。

    党志强
  • [译] Android 上一次编写,随处测试

    在今年的 Google I/O 大会上,我们推出了 AndroidX Test,作为 Jetpack 的一部分。今天,我们很高兴地宣布 v1.0.0 最终版本和...

    Android 开发者

扫码关注云+社区

领取腾讯云代金券