前言
CVE-2025-21298 漏洞其实是在三个月前被研究人员发现的,然后于 2025 年 1 月被微软官方披露,该漏洞 CVSS 评分 9.8 分。
当受害者在 Microsoft Outlook 中打开或预览电子邮件时,漏洞就会被触发,从而允许攻击者在受影响的系统上执行任意代码。
POC
Github 已放出该漏洞的概念验证(PoC),通过演示视频可以看到这是一个内存损坏的 PoC,并非漏洞利用程序(EXP),但不得不说,这种零点击(0-Click)漏洞非常讨“黑客”们的喜爱。
https://github.com/ynwarcs/CVE-2025-21298
漏洞成因
漏洞主要位于ole32.dll!utoLepresstmtocontentsstm中,该函数的目的是将OLE存储中的“Olepres”流中的数据转换为适当格式的数据,然后将其插入同一存储中的“CONTENTS”流中,它接收一个指向存储对象的IStorage指针和三个不重要的参数。
通过查看该函数的实现,并进行补丁差异对比可以看到:
__int64 __fastcall UtOlePresStmToContentsStm(IStorage *pstg, wchar_t *puiStatus, __int64,a3, unsigned int *lpszPresStm)[出自:jiwo.org]
{
struct IStorageVtbl *lpVtbl;// rax
int v7; // r14d
+bool IsEnabled; // al
IStream *v10; // rcx
bool v11; // zf
struct IStorageVtbl *v12;// rax
int v13; // ebx
HRESULT v14; // eax
constwchar_t *v15; // rdx
IStream *pstmContents; // [rsp+40h] [rbp-19h] BYREF
IStream *pstmOlePres; // [rsp+48h] [rbp-11h] BYREF
tagFORMATETC foretc; // [rsp+50h] [rbp-9h] BYREF
tagHDIBFILEHDR hdfh; // [rsp+70h] [rbp+17h] BYREF
*lpszPresStm = 0;
lpVtbl = pstg->lpVtbl;
pstmContents = 0LL;
v7 = 1;
// Create a "CONTENTS" stream in the storage and store it into pstmContents
if ( (lpVtbl->CreateStream)(pstg, L"CONTENTS", 18LL, 0LL, 0, &pstmContents) )
return0LL;
// Immediately release pstmContents, we're not going to be using it right now
(pstmContents->lpVtbl->Release)(pstmContents);
+IsEnabled = wil::details::FeatureImpl<__WilFeatureTraits_Feature_3047977275>::__private_IsEnabled(&`wil::Feature<__WilFeatureTraits_Feature_3047977275>::GetImpl'::`2'::impl);
+v10 = pstmContents;
+v11 = !IsEnabled;
v12 = pstg->lpVtbl;
+if ( !v11 )
+ v10 = 0LL;
+ pstmContents = v10;
(v12->DestroyElement)(pstg, L"CONTENTS");
v13 = (pstg->lpVtbl->OpenStream)(pstg, &OlePres, 0LL, 16LL, 0, &pstmOlePres);// 2nd option to fail -> no OlePres stream
if ( v13 )
{
*lpszPresStm |= 1u;
if ( (pstg->lpVtbl->OpenStream)(pstg, L"CONTENTS", 0LL, 16LL, 0, &pstmContents) )
{
*lpszPresStm |= 2u;
}
else
{
(pstmContents->lpVtbl->Release)(pstmContents);
+ wil::details::FeatureImpl<__WilFeatureTraits_Feature_3047977275>::__private_IsEnabled(&`wil::Feature<__WilFeatureTraits_Feature_3047977275>::GetImpl'::`2'::impl);
}
return v13;
}
foretc.ptd = 0LL;
v13 = UtReadOlePresStmHeader(pstmOlePres, &foretc, 0LL, 0LL);
if ( v13 >= 0 )
{
v13 = (pstmOlePres->lpVtbl->Read)(pstmOlePres, &hdfh, 16LL);
if ( v13 >= 0 )
{
v13 = OpenOrCreateStream(pstg, L"CONTENTS", &pstmContents);
if ( v13 < 0 )
{
*lpszPresStm |= 2u;
goto $errRtn_197;
}
if ( foretc.dwAspect == 4 )
{
*lpszPresStm |= 4u;
v7 = 0;
v13 = 0;
goto $errRtn_197;
}
if ( foretc.cfFormat == 8 )
{
v14 = UtDIBStmToDIBFileStm(pstmOlePres, hdfh.dwSize, pstmContents);
LABEL_19:
v13 = v14;
goto $errRtn_197;
}
if ( foretc.cfFormat == 3 )
{
v14 = UtMFStmToPlaceableMFStm(pstmOlePres, hdfh.dwSize, hdfh.dwWidth, hdfh.dwHeight, pstmContents);
goto LABEL_19;
}
v13 = -2147221398;
}
}
$errRtn_197:
if ( pstmOlePres )
(pstmOlePres->lpVtbl->Release)(pstmOlePres);
// Release pstmContents if it still exists, we need to clean up
if ( pstmContents )
(pstmContents->lpVtbl->Release)(pstmContents);
if ( foretc.ptd )
CoTaskMemFree(foretc.ptd);
if ( v13 )
{
v15 = L"CONTENTS";
goto LABEL_31;
}
if ( v7 )
{
v15 = &OlePres;
LABEL_31:
(pstg->lpVtbl->DestroyElement)(pstg, v15);
}
return v13;
}
问题主要出在pstmContents变量,最初它用于将指针存储到在函数开头创建的“CONTENTS”流对象,创建后立即销毁该流,并释放存储在pstmContents中的指针(在coml2.dll!ExposedStream::~ExposedStream中释放了它)。
然而该变量仍然包含已释放的指针,在函数的后续部分,变量可能会被重新使用,以再次存储指向 "CONTENTS "流的指针--正因为如此,函数末尾有清理代码来释放指针,以防它在变量中存储,代码未能考虑到UtReadOlePresStmHeader可能会失败。
如果发生这种情况,pstmContents仍将指向已释放的指针,从而进入清理代码,再次释放指针,那么就会出现经典的双重释放(UAF)的情况。
在补丁对比中可以看到,微软通过在包含它的指针最初释放后将pstmContents设置为零修复了这个问题。
漏洞复现
首先打开 Word:

然后使用 WinDbg 附加到正在运行的 WINWORD.EXE 进程中:

打开Github 中给出的 poc.rtf ,就会看到 WORD 崩溃的调试信息:

缓解措施与建议
微软已发布补丁来修复该漏洞,强烈建议用户和组织及时应用这些更新来降低潜在风险。
参考资料:https://www.offsec.com/blog/cve-2025-21298/