既然提到了备份网站估计也是存在着网站备份文件,可以先用御剑扫一下
啥都没扫出来,但是上回做文件备份的题目时收集了一些关于常用备份文件的文件名和后缀,可以直接使用burp抓包爆破,果然爆破出一个www.zip文件
访问下载好文件就有三个php文件,下面进行代码审计
访问index.php
首先访问一哈index.php 里面主要是网页代码,不过中间插了一段
包含了class.php文件
定义了一个存放GET请求方法的变量select
将select变量的值进行反序化为 PHP 对象 @表示忽略可能出现的错误信息
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
unserialize() 函数
unserialize() 函数用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。
语法结构
unserialize ($str )
$str:序列化的字符串
如果传递的字符串不可解序列化,则返回 FALSE,并抛出异常
如果不可解序列化,则返回false
serialize() 函数
serialize() 函数用于序列化对象或数组,并返回一个字符串。
serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。
语法
serialize ($value )
$value: 要序列化的对象或数组。
可以看到,当序列化以后输出的结果是一串字符。
a:表示一个数组,由array首字符表示,例如int序列化后回显就是i
4:表示数组的元素个数为4
{}:数组元素的集合(下标 +元素)
i:表示是整型,0:表示元素内容,这里是数组下标
s:表示类型为字符串,长度为6,内容为:Google
访问class.php
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->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') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
首先导入flag.php文件,定义了一个叫Name的类,声明了两个私有变量(顾名思义:变量属性或方法只能在Name类中使用)
__construct
构造函数(也称为构造器)是类中的一种特殊函数,当使用 new 关键字实例化一个对象时,构造函数将会自动调用。代码中用到了 $this,它表示当前调用的对象,而且 $this 只能在类的方法中使用
以下代码大致的意思就是:
定义了一个名为Web的类,该类有三个属性:name、url和title,以及一个构造函数__construct()。构造函数在创建Website对象时自动调用,它接受三个参数并将它们分别赋值给类的属性。
构造函数还调用了类的另一个方法ec(),该方法会输出对象的属性值。
最后,创建了一个Web对象$object,并传入了三个参数。在构造函数中,$object的属性被赋值并输出。因为类无法直接访问,需要得到类的具体对象才能访问,可以通过实例化new来实现对象的创建
__wakeup()
__wakeup():当类外部使用`unserialize()` 时,会检查是否存在一个 `__wakeup()` 方法。如果存在,则会先调用 `__wakeup` 方法,预先准备对象需要的资源。
作用:
__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
这里的作用就是先将username赋值,使我们传入的username永远为guest
__destruct():
析构函数/方法,析构函数的作用和构造函数正好相反,析构函数只有在对象被垃圾收集器收集前(即对象从内存中删除之前)才会被自动调用。析构函数允许我们在销毁一个对象之前执行一些特定的操作,例如关闭文件、释放结果集等。
作用:
这里的作用是,在wakeup函数调用完后,进行判断,如果password不为100为真:抛出错误回显,并利用die()函数退出当前脚本
如果为假,也就是password=100,接着强类型比较username是否=== admin 如果为真就打印flag
所以到现在为止,唯一的问题就是当我们给username赋值后,因为脚本引用了unserialize() 函数,所以,wakeup方法将会再给username赋值为guest,我们得想方法绕过
经过查看wp得知,在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup(),去执行__destruct
但是好像是因为我PHP版本有点高所以不受影响,不能展示了,直接构造吧
解题步骤
首先我们需要获取传入参数的序列化字符串
然后赋值字符串,构造payload,但是并没有成功
?select=O:4:"Name":3:{s:14:Nameusername;s:5:admin;s:14:Namepassword;i:100;}
查看wp后得到有关私有变量的解释
public 、public、protected
类中的 公有、私有、受保护成员
public 表示全局,类内部外部子类都可以访问;
private表示私有的,只有本类内部可以使用;
protected表示受保护的,只有本类或子类或父类中可以访问;
不同的访问修饰符对应的序列化也有不同。
各访问修饰符序列化后的区别:
public:属性被序列化的时候属性名还是原来的属性名,没有任何改变
protected:属性被序列化的时候属性名会变成\0*\0字段名
private:属性被序列化的时候属性名会变成\0类名\0字段名
所以我们重新构造payload,这里利用%00表示\0
/?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
获得flag