phar反序列化及一些场景

旨在对phar反序列化做一个归纳总结

介绍

什么是phar伪协议?

  • 属于php伪协议的一种,phar协议的作用就是归档

  • 简单来说phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php 访问并执行

  • phar 文件包在生成时会以序列化的形式存储用户自定义的 meta-data

phar文件的格式

翻阅手册可以知道,phar由四个部分组成,分别是stub、manifest describing the contents、 the file contents、 [optional] a signature for verifying Phar integrity (phar file format only),以下是对详细的介绍:

  • stubphar文件标识,格式为 xxx<?php xxx; __HALT_COMPILER();?>
  • manifest:压缩文件的属性等信息,以序列化存储
  • contents:压缩文件的内容
  • signature:签名,放在文件末尾

注意:

  • 文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者pdf文件来绕过一些上传限制
  • 我们所需的攻击利用点meta-data序列化信息也在这第二部分中
  • 结合之前的测试,除了上传文件外,文件写入也是可以利用的点,但是需要注意,文件写入很多时候并不是完全可控,但是只要写入最后面的内容可控即可,像那些加了\n的是不可以利用的

相关函数

网上其实很多,但是不全而且存在错误,经测试都是闹着玩

写个脚本自己跑一下

首先获取到所有的php内置函数

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

$a = array_values(get_defined_functions())[0];

$fp = fopen("aaa.txt","a");
foreach($a as $value){
$c = $value."\n";
fwrite($fp,$c);
}
fclose($fp);

自动化测试

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import requests


content = '''<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function test()
{
function_location
}
}
'''
path = "E:/phpstudy/directory/phpstudy_pro/WWW\www.tp6.com/app/controller/Index.php"
address = "http://127.0.0.1:89/index.php/index/test"


def write_file(file_name, contents):
with open(file_name, "w") as f:
f.write(contents)
f.close()


def func1(func1):
str1 = "function('phar://test.phar/test.txt');"
str1 = str1.replace("function", func1)
str2 = content.replace("function_location", str1)
write_file(path, str2)
try:
rsp = requests.get(address, timeout=2)
except:
return False
if "api-ms-win-crt-environment-l1-1-0.dll" in str(rsp.text):
return True
else:
return False


def func2(func2):
str1 = "function('phar://test.phar/test.txt','phar://aaaa.txt');"
str1 = str1.replace("function", func2)
str2 = content.replace("function_location", str1)
write_file(path, str2)
try:
rsp = requests.get(address, timeout=2)
except:
return False
if "api-ms-win-crt-environment-l1-1-0.dll" in str(rsp.text):
return True
else:
return False


def func3(func3):
str1 = "function('phar://test.phar/test.txt','phar://aaaa.txt','phar://aaaa.txt');"
str1 = str1.replace("function", func3)
str2 = content.replace("function_location", str1)
write_file(path, str2)
try:
rsp = requests.get(address, timeout=2)
except:
return False
if "api-ms-win-crt-environment-l1-1-0.dll" in str(rsp.text):
return True
else:
return False


def func4(func4):
str1 = "function('phar://test.phar/test.txt','phar://aaaa.txt','phar://aaaa.txt','phar://aaaa.txt');"
str1 = str1.replace("function", func4)
str2 = content.replace("function_location", str1)
write_file(path, str2)
try:
rsp = requests.get(address, timeout=2)
except:
return False
if "api-ms-win-crt-environment-l1-1-0.dll" in str(rsp.text):
return True
else:
return False


with open("aaa.txt", "r") as f1:
functions = f1.readlines()
f1.close()
result_list = []
i = 1
for func in functions:
func_text = func.replace("\n", "")
print(str(i) + "==>" + func_text)
if func1(func_text):
func_text = func_text + "->1"
result_list.append(func_text)
i = i + 1
elif func2(func_text):
func_text = func_text + "->2"
result_list.append(func_text)
i = i + 1
elif func3(func_text):
func_text = func_text + "->3"
result_list.append(func_text)
i = i + 1
elif func4(func_text):
func_text = func_text + "->4"
result_list.append(func_text)
i = i + 1
else:
i = i + 1
pass
with open("result.txt", "a") as f2:
for result in result_list:
result_f = result + "\n"
f2.write(result_f)
f2.close()
print(result_list)

得出简单的可以触发反序列化的函数如下

测试结果,目前测试到的总共是65

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
spl_autoload->1
sha1_file->1
md5_file->1
hash_file->2-2
hash_hmac_file->3-2
hash_update_file
getimagesize->1
unlink->1
highlight_file->1
show_source->1
php_strip_whitespace->1
parse_ini_file->1
readfile->1
rmdir->1
fopen->2
mkdir->1
rename->2
copy->2
file->1
file_get_contents->1
file_put_contents->2
get_meta_tags->1
opendir->1
dir->1
scandir->1
fileatime->1
filectime->1
filegroup->1
fileinode->1
filemtime->1
fileowner->1
fileperms->1
filesize->1
filetype->1
finfo_file->2-2
file_exists->1
is_writable->1
is_writeable->1
is_readable->1
is_executable->1
is_file->1
is_dir->1
is_link->1
stat->1
lstat->1
touch->1
readgzfile->1
gzopen->2
gzfile->1
xmlwriter_open_uri->1
mime_content_type->1
imagecreatefrompng->1
imagecreatefromwebp->1
imagecreatefromgif->1
imagecreatefromjpeg->1
imagecreatefromwbmp->1
imagecreatefromxbm->1
imagecreatefromxpm->1
imagecreatefromgd->1
imagecreatefromgd2->1
imagecreatefrombmp->1
imageloadfont->1
exif_read_data->1
exif_thumbnail->1
exif_imagetype->1

这里有一部分是后续手动加上去的

比如hash_hmac_file->3-2,表示需要三个参数,第二个参数触发phar

其他

zip

这里一开始还是比较奇怪的,open有些类似fopen,为什么不能触发呢

1
2
3
$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');

Postgres

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

mysql

这里可以发散一下重装漏洞,配合mysql的任意读取,不知道能不能行,暂未测试

1
2
3
4
5
6
7
8
9
10
11
<?php
class A {
public $s = '';
public function __wakeup () {
system($this->s);
}
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');

扩展

常规场景及利用不在多说

Trick

过滤开头是phar协议的绕过,还有就是用其他协议封装,不一定支持,php协议还好

  • 大写PHAR,这个协议是不分大小写的
  • compress.bzip2://phar://
  • compress.zlib://phar:///
  • php://filter/resource=phar://

无文件上传

针对场景

  • windows
  • 无法完整的上传文件
  • 反序列化漏洞未授权访问,但是上传需要权限

见《基于无上传的Phar反序列化的利用》

其他

又遇到如下的情况

image-20210708164445907

loadFromFile中触发反序列化

但是前面有个realpath

windows

首先明确的是,这个函数类似于file_exists

  • windows下不受不存在的路径的限制
  • linux下受不存在的路径的限制

结合phar反序列化的小特点

  • 只要路径中存在pahr文件的路径,在文件之后,无论添加什么字符都可以触发反序列化,包括但不限于../

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function test()
{
if(@realpath("phar://test.phar/test.txt/../../../test.phar")){
file_get_contents("phar://test.phar/test.txt/../../../test.phar");
}
}
}

虽然不再像之前那样不报错了

image-20210708165008596

但是并不影响反序列化的触发

image-20210708165036413

end