原文链接:https://www.brokenbrowser.com/abusing-of-protocols/

原作者:Manuel Caballero

译:Holic (知道创宇404安全实验室)

在 10 月 25 日,研究员 @MSEdgeDev twitter 了一个链接,成功引起了我的注意,因为我点击那个链接的时候(在 Chrome 上),Windows 应用商店会自动打开。这对你来说也许不足为奇,但它足以让我感到惊讶。

在我的印象中,Chrome 有这样一个健康的习惯,在打开外部程序之前询问用户是否打开外部程序。但是这次的情况是它直接打开了相应程序,而且没有弹出警告。

这次的差别情况反应引起了我的注意,因为我从来没有允许 Chrome 打开 Windows 商店。有一些插件和协议会自动打开,但我从来没有允许过 Windows 商店这一应用。

Twitter 的短链接重定向至 https://aka.ms/extensions-storecollection ,然后再一次重定向到:ms-windows-store://collection/?CollectionId=edgeExtensions ,Interesting ~.

关于这一协议我不甚了解,因此我马上试着找到与该协议存在多处关联的地方:注册表。搜索 “ms-windows-store” 立即返回了我们在 PackageId 中的字符串,这似乎是 Windows 应用商店的程序。

注意我们也在一个名为 “Windows.Protocol” 的键之中,我稍微上下滚动了一些,以便看看有没有其他的应用程序在其中。然后我发现他们很多拥有自己的注册协议。这便是极好的,因为这直接从浏览器打开了一个新的攻击面。然后我们按 F3 看看是否找到了其他的匹配项。

似乎 ms-windows-store:协议也接受搜索参数,所以我们可以试着直接从 Google Chrome 打开我们的自定义搜索。事实上,Windows 应用商店应用程序好像使用了 Edge 的引擎渲染 HTML,这也是很有趣的地方,因为我们可能尝试进行 XSS 攻击,亦或是针对本地程序,发送一大堆数据然后看看会发生什么。

但是现在我们不会这么干,我们回到注册表上来,按下 F3 看看能找到什么。

这也是很有意思的,因为如果它们用字符串 “URL:”前缀的话,它会给我们快速找到更多的协议的线索。让我们将搜索重置为 “URL:”,看看我们得到什么。按下 [HOME] 键回到注册表的顶部,搜索 “URL:” ,将马上返回第一个匹配的 “URL:about:blank”,以及各位顺便确认下我们有没有疯掉。

再次按下 F3 ,我们找到了 bingnews: 协议,但是这次 Chrome 向我们确认了是否要打开它。没毛病,让我们在 Edge 上试试看会发生什么。它打开了!在注册表中下一个匹配的的是 calculator: 协议。这会生效吗?

Wow!exploit 的作者们肯定好气啊。它们将弹出什么程序呢?calcnotepad 可以打开,而且没有产生内存损坏。现在 cmd.exe 已经弃用,而是采用了 powershell。微软毁了你们这群人的乐趣 😛 。

这便是枚举所有可能被加载的协议的时候了,先去看看哪些程序接受参数,那么我们可以尝试注入代码(二进制或者纯 Javascript,取决于应用程序的编码方式和他如何处理参数)。有很多有趣的玩法,如果我们继续寻找协议,我们将发现大量的能打开的程序(包括 Candy Crush,我还不知道我电脑上有这东西)。

通过按这几次 F3 ,我受益匪浅。例如,有一个 microsoft-edge:协议在新标签中加载 URL。这看起来似乎并不重要,直到我们想起 HTML 页面应有的限制。弹出窗口拦截器会阻止我们打开 20 个 microsoft-edge:http://www.google.com 标签吗?

[ PoC – 在微软 Edge 浏览器上弹窗 ]

那么 HTML5 沙箱又怎样呢?如果你不熟悉它,它只是一种使用 iframe 沙箱属性或者 http header 的沙箱属性对网页施加限制的方法。例如,如果我们想在 iframe 中渲染内容并且确保它不运行 javascript (甚至不打开新标签),我们只需要使用此标签:

<iframe src=”sandboxed.html” sandbox></iframe>  

然后渲染的页面将被完全限制。它基本上只能渲染 HTML/CSS ,但是没有 javascript 或者其他访问接触到像 cookie 这样的东西。事实上,如果我们使用沙盒粒度,并且至少允许打开新窗口/标签,他们应该全都继承沙箱属性,以及从 iframe 点击链接打开的依然受沙盒限制。然而,使用 microsoft-edge 协议完全绕过了这一点。

[ PoC – 在 微软 Edge 浏览器上绕过 HTML5 沙箱 ]

很高兴看到 microsoft-edge 协议允许我们绕过不同的限制。我更深入研究,但你可以一试!这是一次发现之旅,纪念这一条 tweet 激发了我研究的动力,而且最终给我们真正值得进行更多研究的材料。

我继续在注册表中按下 F3 键,发现了 read: 协议,它引起了我的注意力,因为当阅读它的 (javascript)源码时,它可能有潜在的 UXSS 漏洞,但是尝试的过程中 Edge 一次次地崩溃了。它崩溃太多次了。例如,将 iframe 的 location 设置为 “read:” 就足以使浏览器崩溃,包括所有选项卡。想看看吗?

[ PoC – Crash on MS Edge ]

OK,我很好奇发生了什么,所以我附加了几个字节到 read 协议,并启动了 WinDbg 看看崩溃是不是和无效数据有关。这些东西迅速且简单,没有 fuzzing 或任何特殊的东西:read:xncbmx,qwieiwqeiu;asjdiw!@#$%^&*

Oh yes,我真的打出来了这些东西。我发现的不会使 read 协议崩溃的唯一方法就是加载来自 http[s]的东西。其他的方法都会使浏览器崩溃。

那么让我们将 WinDbg 附加至 Edge 浏览器吧。有一个快速的脏方法,我使用它来简单地杀死 Edge 进程和子进程,重新打开它并附加到使用 EdgeHtml.dll 的最新进程。当然还有更简单的方法,但是...yeah,我就是这么做的。打开命令行,然后...

taskkill /f /t /im MicrosoftEdge.exe

** Open Edge and load the webpage but make sure it doesn't crash yet **

tasklist /m EdgeHtml.dll  

够了。现在加载 WinDbg ,并将其附加到使用 EdgeHtml 的,最新列出的 Edge 进程。记得在 WinDbg 中使用调试符号。

一旦附加上去,只需要按 F5 或者在 WinDbg 中按 g [回车],使 Edge 保持运行。这是我屏幕现在看起来的样子。左边有我用来测试一切的页面,在右边, WinDbg 附加到特定的 Edge 进程。

我们将使用 window.open 伴以 read: 协议继续玩耍,而不是一个 iframe ,因为它使用起来更舒服。仔细想想,有的协议/url 可能会最终改变顶部 location,无论它们如何使用框架。

如果我们开始在 iframe 中使用协议,我们自己的页面(顶部)将有可能被卸载,失去我们刚刚键入的代码。我特定的测试页面保存了我键入的内容,如果浏览器崩溃,它很可能被恢复。但即使一切都保存下来了,当我编写一些可以改变我测试页面的 URL 的代码时,我就在一个新窗口中打开它。这只是一种习惯罢了。

在左侧屏幕上,我们可以快速键入并执行 JavaScript 代码,右侧有 WinDbg 准备向我们解释在崩溃的背后到底发生了什么。我们继续,运行 JavaScript 代码以及... Bang! WinDbg 中断了连接。

ModLoad: ce960000 ce996000 C:\Windows\SYSTEM32\XmlLite.dll  
ModLoad: c4110000 c4161000 C:\Windows\System32\OneCoreCommonProxyStub.dll  
ModLoad: d6a20000 d6ab8000 C:\Windows\SYSTEM32\sxs.dll

(2c90.33f0): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!)
EdgeContent!wil::details::ReportFailure+0x120:  
84347de0 cd29 int 29h

OK,看来 Edge 知道出了问题,因为它位于一个叫做 “ReportFailure” 的函数中,对吧?得了,我知道我们可以马上假设,如果 Edge 在此崩溃,不至于有失“优雅”。所以我们检查 stack trace 来看看我们调用自何方。在 WinDbg 中输入 “k” 。

0:030> k  
# Child-SP RetAddr Call Site
00 af248b30 88087f80 EdgeContent!wil::details::ReportFailure+0x120  
01 af24a070 880659a5 EdgeContent!wil::details::ReportFailure_Hr+0x44  
02 af24a0d0 8810695c EdgeContent!wil::details::in1diag3::FailFast_Hr+0x29  
03 af24a120 88101bcb EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c  
04 af24a170 880da669 EdgeContent!CReadingModeViewer::Load+0x6b  
05 af24a1b0 880da5ab EdgeContent!CBrowserTab::_ReadingModeViewerLoadViaPersistMoniker+0x85  
06 af24a200 880da882 EdgeContent!CBrowserTab::_ReadingModeViewerLoad+0x3f  
07 af24a240 880da278 EdgeContent!CBrowserTab::_ShowReadingModeViewer+0xb2  
08 af24a280 88079a9e EdgeContent!CBrowserTab::_EnterReadingMode+0x224  
09 af24a320 d9e4b1d9 EdgeContent!BrowserTelemetry::Instance::2::dynamic  
0a af24a3c0 8810053e shlwapi!IUnknown_Exec+0x79  
0b af24a440 880fee33 EdgeContent!CReadingModeController::_NavigateToUrl+0x52  
0c af24a4a0 88074f98 EdgeContent!CReadingModeController::Open+0x1d3  
0d af24a500 b07df508 EdgeContent!BrowserTelemetry::Instance'::2::dynamic  
0e af24a5d0 b0768c47 edgehtml!FireEvent_BeforeNavigate+0x118  

看看前两行,都叫做 blah blah ReportFailure ,你不觉得 Edge 是因为出现错误了吗才运行到这里的吗?当然!让我们继续运行下去,直到我们找到一个有意义的函数名。下一个叫做 blah FallFast,这便有一些 Edge 知道出现错误才会调用它的味道。但是我们想找到使 Edge 出现问题的代码,那么继续读下去吧。

下一个是 blah _loadRMHTML。这个对我来说看起来好多了,你难道不也这么认为吗?事实上,他的名字让我觉得它是加载 HTML 的。在崩溃之前断下程序的话,这将会变得有意思多了,所以为什么不在 _LoadRMHTML 上面几行设置断点呢?我们检查了 stack-trace,现在我们来看看代码。

我们先从那个断点(函数+偏移)查看反汇编。这很简单,在 WinDbg 中使用 “ub” 命令。

0:030> ub EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c  
EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a:  
8810693a call qword ptr [EdgeContent!_imp_SHCreateStreamOnFileEx (882562a8)]  
88106940 test eax,eax  
88106942 jns EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7d (8810695d)  
88106944 mov rcx,qword ptr [rbp+18h]  
88106948 lea r8,[EdgeContent!`string (88261320)]  
8810694f mov r9d,eax  
88106952 mov edx,1Fh  
88106957 call EdgeContent!wil::details::in1diag3::FailFast_Hr (8806597c)

我们只关注名字,忽略其他东西,好伐?就像我们在《trying to find a variation for the mimeType bug》中一样,我们在此投机取巧,当然如果我们失败了就继续深入。但有时在调试器上的快速查看能够阐明很多事情。

我们知道如果 Edge 到达这个片段的最后一条指令(地址为 88106957,FailFast_Hr),Edge 就会崩溃掉。我们的目标是弄清我们最终运行到的地方,就是说谁把我带到那里的。上面的代码的第一条指令似乎是调用了一个复杂名称的函数,这显然大量体现了我们的东西。

EdgeContent!_imp_SHCreateStreamOnFileEx

在 ! 前的第一部分是该指令所在的模块(exe,dll等等...)。这种情况下是 EdgeContent ,我们甚至不关心它的扩展名,它只是一段代码。! 之后有个有趣的函数名叫_imp_ ,然后 SHCreateStreamOnFileEx 似乎是一个“创建文件流”的函数名。你同意吗?事实上,_imp_的部分让我想起这可能是从不同的二进制文件加载的导入函数。让我 google 一下这个名字,看看能不能找到有趣的东西。

这太棒了。第一个结果正是我们搜索的准确名称。让我们来点击一下。

好。此函数接收的第一个参数是 “A pointer to a null-terminated string that specifies the file name”(指向以空字符结尾的字符串指针,该字符串指定文件名) 。因垂丝挺!如果这段代码正被执行,那么它应该接收一个指向文件名的指针作为第一个参数。但是我们这么能看到第一个参数呢?很简单,我们在 Win x64上运行,调用约定/参数解析说,“前四个参数是 RCX, RDX, R8, R9 ”(表示整数/指针)。这意味着第一个参数(指向文件名的指针)将被装载入 RCX 寄存器。

有了这些信息,我们可以在 Edge 调用之前设置一个断点,看看 RCX 在那个确定时刻有何值。但是我们重新启动一遍程序吧,因为这时已经有点迟了:Edge 已经崩溃了。请重新按照上面描述的做一遍(杀掉 Edge 进程,打开它,加载页面,找到进程并附加上去)。

这个时候,不要运行(F5)进程,我们先设置一个断点。WinDbg 显示了我们执行 “ub” 命令时的确切偏移量。

0:030> ub EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c  
EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a:  
8810693a ff1568f91400 call qword ptr [EdgeContent!_imp_SHCreateStreamOnFileEx (882562a8)]  
88106940 85c0 test eax,eax  

所以断点应该在 EdgeContent!CReadingModeViewerEdge :: _ LoadRMHTML 0x5a 处。我们键入 “bp” 和函数名 + 偏移[回车]。然后 “g” 让 Edge 继续运行。

0:029> bp EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a  
0:029> g  

这很一颗赛艇。在 SHCreateStreamOnFileEx 执行之前,我们想要看到 RCX 指向的文件名(或者字符串)。我们运行代码,稍适小憩。好吧,宝宝我感受到它了 =) 断点连至我的童年。让我们运行这段 JavaScript 代码吧,bang!WinDbg 在此中断。

Breakpoint 0 hit  
EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a:  
8820693a ff1568f91400 call qword ptr [EdgeContent!_imp_SHCreateStreamOnFileEx (883562a8)]

这太棒了,现在我们可以检查 RCX 指向的内容。为此我们使用 “d” 命令(显示内存)@ 加上寄存器名称,如下所示:

0:030> d @rcx  
02fac908 71 00 77 00 69 00 65 00-69 00 77 00 71 00 65 00 q.w.i.e.i.w.q.e.  
02fac918 69 00 75 00 3b 00 61 00-73 00 6a 00 64 00 69 00 i.u.;.a.s.j.d.i.  
02fac928 77 00 21 00 40 00 23 00-24 00 25 00 5e 00 26 00 w.!.@.#.$.%.^.&.  
02fac938 2a 00 00 00 00 00 08 00-60 9e f8 02 db 01 00 00 *.......`.......  
02fac948 10 a9 70 02 db 01 00 00-01 00 00 00 00 00 00 00 ..p.............  
02fac958 05 00 00 00 00 00 00 00-00 00 00 00 19 6c 01 00 .............l..  
02fac968 44 14 00 37 62 de 77 46-9d 68 27 f3 e0 92 00 00 D..7b.wF.h'.....  
02fac978 00 00 00 00 00 00 08 00-00 00 00 00 00 00 00 00 ................  

这对我的眼睛很不友好,但在第一行的右边,我看到了一些类似于 Unicode 字符串的东西。我们将它显示为Unicode字符吧(du 命令)。

0:030> du @rcx  
02fac908 "qwieiwqeiu;asjdiw!@#$%^&*"  

Nice!字符串将我包围!看看我们刚才运行的 JavaScript 代码。

看来,传给这个函数的参数是逗号后面输入的任何内容。有了这点知识加上知道它期望获取一个文件名,我们可以尝试一个在硬盘上的完整的路径。因为 Edge 在 AppContainer 内部运行,我们将尝试一个可访问的文件。例如来自 windows/system32 目录的内容。

read:,c:\windows\system32\drivers\etc\hosts  

我们同时删除逗号之前的垃圾数据,看起来似乎无关紧要(虽然他值得进行进行更多研究)。我们快速分离,重启 Edge,并运行我们的新代码。

url = "read:,c:\\windows\\system32\\drivers\\etc\\hosts";

w = window.open(url, "", "width=300,height=300");  

如预期所想,在新窗口中加载本地文件并没有崩溃。

[ PoC – Open hosts on MS Edge ]

遵循 bug hunter,我将在此停顿,但我相信所有的这些值得进一步的研究,取决于你的兴趣了:

A)枚举所有可加载的协议,并通过查询字符串攻击应用程序

B) 调戏 microsoft-edge: 绕过 HTML5 沙盒,弹出窗口拦截器和谁会知道的东西。

C) 继续使用 read: 协议。我们找到了一种方法来阻止它崩溃,但记住有一个函数 SHCreateStreamOnFileEx ,我们能够影响它的期望值!这值得更多的尝试。此外,我们可以继续对传入参数进行研究,看看是否使用逗号分隔参数等等。如果调试二进制无聊至极,那么你仍然可以尝试对阅读视图进行 XSS。

希望你能找到成吨的漏洞!如果你有问题,请在@magicmac2000 上 ping 我。

Have a nice day!

Reported to MSRC on 2016-10-26