一个修了九个月的洞,和一个不会攻击的PoC:CVE-2026-21509结构样本解剖

admin 2026-11-13 10:35:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

一个修了九个月的洞,和一个不会攻击的 PoC:CVE-2026-21509 结构样本解剖

Ots安全

2026年11月12日 13:45
广东

去阅读

2026 年 1 月 28 日,GitHub 上一个只有两个文件的仓库完成了第三次提交。仓库名写着 CVE-2026-21509-PoC,https://github.com/gavz/CVE-2026-21509-PoC 点进去却没有利用链,没有载荷,作者用粗体在开头声明这是一份不会攻击的样本。它做的事情只是用 237 行 Python 生成一个 DOCX,往里塞一个 1024 字节的占位文件和一张图标。把它交给 Office 打开,什么都不会触发。

这个洞本身并不温和。CVE-2026-21509%20是微软%20Office%20的安全功能绕过,2026%20年%201%20月%2026%20日被列入%20CISA%20已知被利用漏洞目录,微软当天发布带外补丁。到本文成稿的%2011%20月%2012%20日,修复已经过去约九个半月。

把这两件事放在一起,正好回答一个对防守方更有价值的问题:一个已经修好、又不会攻击的样本,为什么还值得拆开看。答案藏在结构里。这份样本把%20Office%20判断%20OLE%20对象时依赖的那些字段单独拎出来,做成了一份可以拿去喂%20EDR、写规则、训练分析师的标本。它测的不是漏洞能不能打穿,而是检测能不能看见。

已关注

关注

重播%20%20%20%20分享%20%20%20%20%20赞

关闭

观看更多

更多

退出全屏

切换到竖屏全屏退出全屏

Ots安全已关注

分享视频

,时长00:42

0/0

00:00/00:42

切换到横屏模式

继续播放

[%20]

进度条,百分之0

播放

00:00

/

00:42

00:42

倍速

全屏

倍速播放中

0.5倍0.75倍1.0倍1.5倍2.0倍

超清流畅

继续观看

一个修了九个月的洞,和一个不会攻击的%20PoC:CVE-2026-21509%20结构样本解剖

观看更多

转载

,

一个修了九个月的洞,和一个不会攻击的%20PoC:CVE-2026-21509%20结构样本解剖

Ots安全已关注

分享点赞在看

已同步到看一看写下你的评论

视频详情

一、先看问题:评分、时间线、官方口径与偏差全在这里

先把能钉死的事实摆出来,再把互相打架的说法并列挂着,不替任何一方填平。

1.1%20官方怎么描述这个洞

美国国家漏洞库%20NVD%20收录的描述原文是:Reliance%20on%20untrusted%20inputs%20in%20a%20security%20decision%20in%20Microsoft%20Office%20allows%20an%20unauthorized%20attacker%20to%20bypass%20a%20security%20feature%20locally.%20译成中文,是微软%20Office%20在一项安全决策中依赖了不受信任的输入,使未授权攻击者可以在本地绕过一项安全功能。

微软安全响应中心补了关键的一层:该更新修复的是一处绕过%20OLE%20缓解的问题,这些缓解原本用来保护用户免受脆弱的%20COM%20或%20OLE%20控件影响。把两句话合起来读,边界就清楚了。这是判定逻辑被误导,不是内存破坏,也不是默认就等同于远程代码执行。官方定性写的是安全功能绕过,弱点编号是%20CWE-807,即在安全决策中依赖不受信任的输入。

评分同样需要分层。微软作为%20CNA%20给出%20CVSS%203.1%20的%207.8,向量为%20AV:L%20/%20AC:L%20/%20PR:N%20/%20UI:R%20/%20S:U%20/%20C:H%20/%20I:H%20/%20A:H。NVD%20自身尚未给出评估,页面显示%20N/A。一份巴基斯坦国家%20CERT%20的公告写过%208.6,并标注为上下文评分,不是官方数值,本文不采用。向量里%20AV:L%20与%20UI:R%20两个字段值得留意:攻击在本地发生,且需要受害者打开文件。

1.2%20时间线:修复窗口已经很长
日期 事件 出处层级
2026-01-26 NVD%20收录%20CVE-2026-21509,CISA%20同日列入%20KEV 官方
2026-01-27 微软发布带外补丁,多家媒体同步披露 官方加媒体
2026-01-28 该仓库三次提交,脚本署名%20IncursioHack%20v1.0.0 一手源码
2026-01-29 仓库创建时间记录 平台元数据
2026-02-16 CISA%20KEV%20要求的处置截止日 官方
2026-06-25 NVD%20记录最近一次修改 官方
2026-11-09 仓库元数据更新,代码未再改动 平台元数据
2026-11-12 本文成稿 本文

从%20KEV%20收录到本文成稿,跨度约%20290%20天。仓库的%20pushed%20时间仍停在%201%20月%2028%20日,11%20月%209%20日那次更新只是星标与分叉计数变动,没有新代码。这是一份旧洞的旧样本,不是新爆发的威胁。防守方现在翻它出来,目的应当是补检测、清存量,而不是追热点。

1.3%20偏差清单:能写实的,和只能挂着的
议题 一手或主流口径 第三方说法 本文处理
漏洞性质 安全功能绕过,CWE-807 部分公告与聚合站写成远程代码执行或任意代码执行 按官方写绕过,说明后果是通道而非既成事实
CVSS 微软%20CNA%207.8%20High,NVD%20自身%20N/A 地方%20CERT%20写上下文%208.6 采用%207.8,其余并列标注
在野归因 微软未公开攻击者与活动细节 有情报厂商称%20APT28%20针对乌克兰,投放多阶段加载器 归因作为第三方判断单列,不作官方结论
仓库作者 仓库归属账号%20gavz,提交与脚本署名为%20IncursioHack 部分转述把两者混为一人 分开写,说明身份差异
图片素材 仓库只有%20README%20与%20Python%20源码,零张图片 无 如实说明,机制图由本文绘制
样本能力 README%20反复声明不利用、不加载%20COM、不执行代码 情报平台把它与其他可利用%20PoC%20并列收录 明确这是结构样本,验证不了绕过是否成功
补丁编号 NVD%20给出具体%20build%20号 聚合站列出多个%20KB%20号,彼此并不一致 以%20build%20号为准,KB%20号标为第三方说法
战术映射 官方未给%20ATT&CK%20编号 某情报平台标%20T1190%20面向公网应用利用 与该洞需打开本地文件的前提不吻合,不采用
1.4%20开放问题钉墙

三件事公开信息没有说明,本文不猜。其一,被操纵的具体输入字段是什么,微软公告没有点名,社区分析给出的%20CLSID%20伪造与注册表重定向只是推测路径。其二,在野攻击使用的载荷、投递规模与目标清单,微软截至成稿未披露。其三,该样本能否在打补丁前后稳定呈现不同提示,仓库没有给出对照实验,作者只提供结构,不承诺可复现绕过。

三处缺口都不影响防守价值。检测面的成立不依赖知道攻击者填了哪个字段,只依赖知道文档里会出现哪些结构特征。

二、影响范围:版本、打开动作与生效条件

影响范围用条件式判定,不靠版本号堆砌。一条主机是否暴露,取决于四个条件同时成立。

2.1%20版本与构建

受影响的家族包括%20Office%202016、Office%202019、Office%20LTSC%202021、Office%20LTSC%202024,以及%20Microsoft%20365%20Apps%20for%20Enterprise。NVD%20给出的修复阈值分两类:需要本地安装的旧版,Office%202016%20修到%2016.0.5539.1001,Office%202019%20修到%2016.0.10417.20095;服务端下发的新版,LTSC%202021、LTSC%202024%20与%20365%20Apps%20的阈值指向微软的发布页,由服务端变更统一保护。32%20位与%2064%20位版本的阈值相同。

这里有个容易踩空的点。CVE%20记录里的%20CPE%20把%20Office%202019%20的起始版本写成%2019.0.0,修复阈值却是%2016.0.10417.20095,数字前后并不自洽。这是版本命名空间的老问题,工单里以微软构建号为准,不要拿%2019%20与%2016%20直接比大小。

2.2%20打开动作

官方明确两点。攻击需要用户交互,受害者得打开文件;预览窗格不是攻击向量。这两条把场景限定得很死:它不是邮件预览即中的那一类,也不需要受害者启用宏。因此邮件网关里只靠拦宏、只靠查预览行为,覆盖不到这条链。真正的入口是一次普通的双击打开。

2.3%20组件与配置

绕过针对的是%20OLE%20缓解,能不能走到加载那一步,还取决于目标机器是否允许实例化相应的%20COM%20控件。样本里写死的控件是%20Shell.Explorer.1,对应%20CLSID%20为%20{EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B}。这台机器如果该%20CLSID%20已被%20kill%20bit%20拦截,且拦截判定没有被绕过,控件就不会实例化。

2.4%20处置状态

有些产品可能已经停止支持。CISA%20在%20KEV%20条目里提示,部分受影响产品可能已进入生命周期末端,建议停用或迁移到受支持版本。一台无法再打补丁的旧%20Office,靠注册表缓解只能争取时间,不能替代升级。

四个条件连起来看,暴露面的判断就落地了:版本落在阈值之前、文件被真实打开、组件策略仍允许危险控件加载、且机器尚未完成补丁或服务端的重启生效。断掉其中任意一条,这条链就走不通。

三、原因导致:kill%20bit%20判定读错了什么

要理解这个洞为什么叫安全功能绕过,得先看懂%20Office%20原本靠什么拦住危险控件。

3.1%20kill%20bit%20是一道注册表闸门

Windows%20为%20COM%20组件准备了一套兼容性开关,路径里有%20COM%20Compatibility%20这个节点。某个控件的%20CLSID%20若在该节点下被写入%20Compatibility%20Flags%20并设为%200x400,就等于给这个控件打上%20kill%20bit,Office%20在实例化前会查这道闸门,命中拦截就不再加载。Shell.Explorer.1%20这类过往被反复滥用的控件,正是这道闸门的重点对象。

一个重要澄清:本文所说的具体被操纵输入字段,公开信息未说明。下面这张图复原的是判定逻辑的骨架,不是攻击者填写的字节级细节。

图注:kill%20bit%20正常判定与构造文档下的绕过对照。本文依据微软公告与%20CISA%20KEV%20描述绘制,非原仓库配图,仅作机制示意,不作为漏洞利用证据。

3.2%20绕过发生在判定环节

把两条链叠着看,分歧点很清楚。正常路径里,Office%20读入文档,解析嵌入对象的%20CLSID,去查%20COM%20Compatibility%20的兼容性标志,命中%200x400%20就阻止加载。构造文档下,前面的读入与解析一样发生,但安全决策所依赖的输入不再可信,本该拦截的对象被判成可放行,控件进入进程,随后才可能出现异常子进程或外连。

这不是把闸门拆了,是让闸门读了一本假名册。闸门本身还在,判定结果被输入污染。也正因为如此,修复方向不是加一道新闸门,而是让那本名册不再由文档说了算。

3.3%20根源是一个三十年的老功能

对象链接与嵌入,也就是%20OLE,从早期%20Office%20时代就在支撑复合文档:Word%20里嵌表格,文档里塞图表,甚至包装一个无头浏览器控件。它的能力来自把对象渲染与激活交给外部%20COM%20组件,这份灵活同样是把信任边界弄模糊的根源。文档既是容器,又是可以指定由谁来渲染这张容器的指令集。

学术界在%202025%20年专门讨论过%20Office%20内嵌%20OLE%20对象的复杂度,指出这种复杂度会随年份累积成可利用条件。这条脉络上并不缺先例:CVE-2017-11882%20的公式编辑器、CVE-2017-0199%20的%20HTA%20处理、CVE-2017-8570%20的复合名字对象、CVE-2021-40444%20的%20MSHTML,都是在一条旧路径被堵上之后,攻击者转到下一条仍保留强能力的旧路径。

把这次的%20CVE-2026-21509%20放进这条序列,就能看清它的位置:被滥用的不是新代码,而是一直在、且为了兼容必须一直留着的信任决策。

四、利用链:一个结构样本能证明到哪一步

这一章拆仓库里的那份源码。先说清楚它不做什么,再说它做了什么,收尾给出防守方能从中提取的检测面。

4.1%20先把能力边界钉死

README%20的措辞非常克制,反复出现非利用、供防御研究、供%20EDR%20与杀软可见性测试、供%20SOC%20训练这类表述。脚本自身的收尾打印也写了两行:可被%20EDR%20检测,因为含%20OLE%20结构与%20CLSID;安全,因为没有执行或真实%20COM%20加载。

这意味着样本验证不了绕过是否成功。它只保证产出的%20DOCX%20里带着那几个会被检查的结构字段,供防守方确认自己的检测能否被触发。把它当成可用利用来运行,得到的只会是一份打不开危险控件、也不会执行任何命令的占位文档。这一层区分,恰恰是这个仓库最值得称道的地方。

4.2%20六个部件拼成一张%20OOXML%20外壳

DOCX%20本质是%20ZIP%20包。脚本按%20OOXML%20规范拼出六个部件,再用%20zipfile%20打包。包结构如下图。

图注:脚本生成的%20DOCX%20包结构。本文依据仓库脚本逐段还原,非原仓库配图,仅作结构示意,不作为证据引用。源仓库没有任何图片。

先看那个常量的配置段。整份样本的目标%20CLSID%20就写在这里。

TMP = \"_pkg_docx\"OUT = \"PoC_CVE2026_21509_Structural.docx\"CLSID = \"EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B\"  #%20metadata-only,%20safe

一个常量就是全部敏感字段。所以它根本不构造利用,只把这个%20CLSID%20当作可检索的元数据塞进占位文件。

4.3%20占位%20OLE:1024%20字节里的门道

最能体现防御意图的是生成%20dummy%20OLE%20的函数。

def generate_ole_dummy(path,%20clsid_str): %20 %20clsid_bytes%20=%20uuid.UUID(clsid_str).bytes_le %20 %20header%20 = b\"\\x01\\x05\\x00\\x00\" %20 %20version%20= b\"\\x02\\x00\\x00\\x00\" %20 %20flags%20 %20= b\"\\x0C\\x00\\x00\\x00\"
 %20 %20blob%20=%20header%20+%20version%20+%20flags%20+%20clsid_bytes
 %20  if len(blob)%20< 1024: %20 %20 %20 %20blob%20+= b\"\\x00\" *%20(1024 - len(blob))
 %20  with open(path, \"wb\") as f: %20 %20 %20 %20f.write(blob)

四字节头、四字节版本、四字节标志,加上按小端序排列的%20CLSID%20字节,凑不满%201024%20就用零补齐。这不是一个合法的复合文档二进制流,也没有任何可序列化的对象数据。它是一块带着目标%20CLSID%20字节的填充物。

对防守方,这段给出的检测点非常具体:文档里出现一个约%201024%20字节、开头是固定头、并在偏移处携带该%20CLSID%20小端字节序列的%20bin%20文件,就值得拉出来看。对攻击者,它提供不了任何执行能力。一块零填充里没有可运行的东西。

4.4%20关系文件把三个部件串起来

OOXML%20把对象引用写在关系文件里。脚本生成的两条关系分别是这样。

rel1.set(\"Id\", \"rIdOle1\")rel1.set(\"Type\", \".../relationships/oleObject\")rel1.set(\"Target\", \"embeddings/oleDummy.bin\")
rel2.set(\"Id\", \"rIdImage1\")rel2.set(\"Type\", \".../relationships/image\")rel2.set(\"Target\", \"media/icon.png\")