2020-⽹鼎杯-⻘⻰组-Web-AreUSerialz
题目源码:
include("NewFlag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "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 = NewFlag::getFlag($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]:
";
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); // __wakeup
}
} // __destruct
?>
NewFlag.php 源码:
class NewFlag {
public static function getFlag($fileName) {
$res = "flag error";
if($fileName ==="NewFlag.php") {
$res = "flag:{this is flag}";
}
return $res;
}
}
?>
测试环境:

这是一道经典的 PHP 反序列化漏洞题,下面我会详细说说如何解题。
思路:
首先查看类的成员属性,可以确定$op、$filename、$content这三个成员变量,并且能发现它们的访问权限是protected(受保护的)

1.查看源码发现是get方式提交的,那么在访问的时候可以通过?str=变量,这种方式进行访问,并且在这里得知str进行了反序列化

2.接着阅读源码,发现似乎并没有调用的地方,但再仔细阅读会发现魔术方法 __destruct(),也即析构函数。析构函数的作用是 “清理对象”,只有当对象的生命周期结束时才会被调用。而Web 请求的 PHP 脚本从头到尾执行完,所有创建的对象会被 PHP 引擎自动销毁,此时触发__destruct(); ,阅读这个析构函数使用了一个叫 process()的函数

3.,阅读 process()发现决定这个函数作用的是 "1" 和 "2",分别代表写入的权限和阅读的权限,而我们是需要获取flag,所以read()的权限优先级更高

4.这会调用NewFlag的getFlag这个静态方法并将获取的内容赋予res并返回

5.getFlag里面有flag

思路既然已经清楚了,那么后面则是如果一步步获得flag
步骤:
1.首先观察析构与process(),发现都有op,且op == "2"就能进入read(),并且析构函数中直接就进行了调用,但再仔细观察可以发现我们需要的是op为"2",但在析构函数中一旦完全等于了"2"则会强制将op变为了"1",这个并不是我们想要的,但在process()中,虽然要进read()是需要op为"2",却只是op=="2"是松散比较,这就给了我们绕过的机会,可以op = " 2"来进行绕过,这样可以绕过op的强制等于"1",又可以成功被判定为op为"2"


2.然后是还有一个参数filename,由于刚开始的时候已经包含了NewFlag.php,所以才能直接NewFlag::getFlag()调用这个文件里的方法,那么filename则是需要告诉其是调用的哪个文件的这个方法,所以filename=NewFlag.php

3.我们知道op以及filename是受保护的属性,但在序列化中属性名前会自动加上 * ( 是不可见的 null 字节),而源代码中却有 is_valid(),这个函数的作用是过滤,只会打印可打印的 ASCII 字符,,而 不在这个范围内

4.将类的成员属性的权限改为public,即可干净的序列化转换(因为PHP 反序列化时,只关心「类名」和「属性名」是否匹配,不关心属性的修饰符即权限),所以向我们新建一个test.php,将类再写出来,然后再进行序列化

5.再进行访问就能看到序列化后是什么了

将上面序列化后的复制出来,粘贴进?str=后面即可

6.得到flag(在网页末尾)

本文地址:https://www.vps345.com/25791.html











