On October 25th, the fellows @MSEdgeDev twitted a link that called my attention because when I clicked on it (being on Chrome) the Windows Store App opened. It might not surprise you, but it surprised me!
As far as I remembered, Chrome had this healthy habit of asking the user before opening external programs but in this case it opened directly, without warnings.
This was different and caught my attention because I never accepted to open the Windows Store in Chrome. There are some extensions and protocols that will open automatically but I’ve never approved the Windows Store.
The shortened Twitter link redirected to https://aka.ms/extensions-storecollection which (again) redirected to the interesting: ms-windows-store://collection/?CollectionId=edgeExtensions.It was a protocol that I was not aware of, so I immediately tried to find it in the place where most protocol associations reside: the registry. A search of “ms-windows-store“ immediately returned our string inside the PackageId of the what seemed to be the Windows Store app.
Noting that we were also in a key called “Windows.Protocol” I scrolled up and down a bit to see if there were other apps inside, and found that tons of them (including MS Edge) had their own protocols registered. This is nice because it opens a new attack surface straight from the browser. But let’s press F3 to see if we find other matches.
It seems that the ms-windows-store: protocol is also accepting search arguments, so we can try opening our custom search straight from Google Chrome. In fact, the Windows Store app seems to be rendering HTML with the Edge engine, which is also interesting because we might try to XSS it or if the app is native, send big chunks of data and see what happens.
But we won’t be doing that now, let’s go back to regedit and press F3 to see what else we can find.
This one is interesting also, because it gives us clues on how to quickly find more protocols if they are prepended with the string “URL:”. Let’s reset our search to “URL:” and see what we get. Pressing the [Home] key takes us back to the top of the registry and a search of “URL:” immediately returns the first match “URL:about:blank“, confirming that we are not crazy.
Press F3 again and we find the bingnews: protocol but this time Chrome requests us confirmation to open it. No problem, let’s try it on Edge to see what happens. It opens! Next match in the registry is the calculator: protocol. Will this work?
Wow! I’m sure this will piss off exploit writers. What program will they pop now? Both calc and notepad can be open without memory corruptions, and cmd.exe is deprecated in favor to powershell now. Microsoft removed the fun 😛 out of you guys.
This could be a good moment to enumerate all loadable protocols and see which apps accept arguments so we can try to inject code into them (binary or pure javascript, depending on how the app was coded and how it treats the arguments). There is a lot of interesting stuff here to play with, and if we keep searching for protocols we will find tons of apps that open (including Candy Crush which I didn’t know it was on my PC).
By pressing F3 a few times I learned a lot. For example, there’s a microsoft-edge: protocol that loads URLs in a new tab. It doesn’t seem to be important, until we remember the limits that HTML pages should have. Will the popUp blocker prevent us from opening 20 microsoft-edge:http://www.google.com tabs?
[ PoC – Open popUps on MS Edge ]
What about the HTML5 Sandbox? If you are not familiar with it, it’s just a way to impose restrictions to a webpage using the sandbox iframe attribute or the sandbox http header. For example, if we want to render content inside an iframe and make sure it does not run javascript (not even open new tabs) we can just use this tag:
<iframe src=”sandboxed.html” sandbox></iframe>
And the rendered page will be completely restricted. Essentially it can only render HTML/CSS but no javascript or access to things like cookies. In fact, if we use the sandbox granularity and allow at least new windows/tabs, all of them should inherit the sandboxed attributes and opened links from that iframe will still be sanboxed. However, using the microsoft-edge protocol bypasses this completely.
[ PoC – Bypass HTML5 Sandbox on MS Edge ]
Nice to see that the microsoft-edge protocol allows us to bypass different restrictions. I haven’t went further than that but you can try! This is a journey of discovery, remember that a single tweet fired my motivation to play a bit and ended up giving us stuff that truly deserves more research.
I continued pressing F3 in regedit and found the read: protocol which called my attention because when reading its (javascript) source code. It had the potential for a UXSS but Edge kept crashing again and again while trying. It crashed too much. For example setting the location of an iframe to “read:” was enough to crash the browser including all tabs. Want to see it?
[ PoC – Crash on MS Edge ]
OK, I was curious about what was happening so I appended a few bytes to the read protocol and fired up WinDbg to see if the crash was related to invalid data. Something quick and simple, no fuzzing or anything special: read:xncbmx,qwieiwqeiu;asjdiw!@#$%^&*
Oh yes, I really typed something like that. The only way that I found not to crash the read protocol was to load anything coming from http[s]. Everything else crashed the browser.
So let’s attach WinDbg to Edge. A quick dirty method that I use it to simply kill the Edge process and children, reopen it and attach to the latest process that uses EdgeHtml.dll. Of course there are easier ways but … yeah, I’m just like that. Open a command line and…
taskkill/f/t/imMicrosoftEdge.exe **OpenEdgeandloadthewebpagebutmakesureitdoesn'tcrashyet** tasklist/mEdgeHtml.dll |
Enough. Now load WinDbg and attach to the latest listed Edge process that uses EdgeHtml. And remember to use Symbols in WinDbg.
Once attached, just press F5 or g [ENTER] inside WinDbg so Edge keeps running. This is how my screen looks right now. On the left I have the page that I use to test everything and on the right, WinDbg attached to that particular Edge process.
We will use a window.open to play with the read: protocol instead of an iframe because it’s more comfortable. Think about it, there are protocols/urls that might end up changing the top location regardless of how framed they are.
If we start playing with a protocol inside an iframe there are chances that our own page (the top) will be unloaded, losing the code that we’ve just typed. My particular test-page saves everything that I type, so if the browser crashes it’s highly likely that I will be able to repro my manual work. But even with everything saved, when I’m playing with code that could change the URL of my test-page, I open it in a new window. Just a habit.
On the left screen we can type and execute JavaScript code quickly, on the right we have WinDbg prepared to reveal us what’s happening behind this crash. Go ahead, let’s run the JavaScript code and… Bang! WinDbg breaks.
ModLoad:ce960000ce996000C:\Windows\SYSTEM32\XmlLite.dll ModLoad:c4110000c4161000C:\Windows\System32\OneCoreCommonProxyStub.dll ModLoad:d6a20000d6ab8000C:\Windows\SYSTEM32\sxs.dll (2c90.33f0):Securitycheckfailureorstackbufferoverrun-codec0000409(!!!secondchance!!!) EdgeContent!wil::details::ReportFailure+0x120: 84347de0cd29int29h |
OK, it seems that Edge knew something went wrong because it’s in a function called “ReportFailure”, right? Come on, I know we can immediately assume that if Edge is here, it failed somewhat “gracefully”. So let’s inspect the stack trace to see where are we coming from. Type “k” in WinDbg.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
0:030>k #Child-SPRetAddrCallSite 00af248b3088087f80EdgeContent!wil::details::ReportFailure+0x120 01af24a070880659a5EdgeContent!wil::details::ReportFailure_Hr+0x44 02af24a0d08810695cEdgeContent!wil::details::in1diag3::FailFast_Hr+0x29 03af24a12088101bcbEdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c 04af24a170880da669EdgeContent!CReadingModeViewer::Load+0x6b 05af24a1b0880da5abEdgeContent!CBrowserTab::_ReadingModeViewerLoadViaPersistMoniker+0x85 06af24a200880da882EdgeContent!CBrowserTab::_ReadingModeViewerLoad+0x3f 07af24a240880da278EdgeContent!CBrowserTab::_ShowReadingModeViewer+0xb2 08af24a28088079a9eEdgeContent!CBrowserTab::_EnterReadingMode+0x224 09af24a320d9e4b1d9EdgeContent!BrowserTelemetry::Instance::2::dynamic 0aaf24a3c08810053eshlwapi!IUnknown_Exec+0x79 0baf24a440880fee33EdgeContent!CReadingModeController::_NavigateToUrl+0x52 0caf24a4a088074f98EdgeContent!CReadingModeController::Open+0x1d3 0daf24a500b07df508EdgeContent!BrowserTelemetry::Instance'::2::dynamic 0eaf24a5d0b0768c47edgehtml!FireEvent_BeforeNavigate+0x118 |
Check out the first two lines, both called blah blah ReportFailure, don’t you think Edge is here because something went wrong? Of course! Let’s keep going down until we find a function name that makes sense. The next one is called blah FailFast which also smells like it’s a function Edge called knowing that something went wrong. But we want to find the code that made Edge unhappy so continue reading down.
The next one is blah _LoadRMHTML. This looks much better to me, don’t you agree? In fact, its name makes me think it Loads HTML. It would be interesting to break before the crash, so why not setting a breakpoint a few lines above _LoadRMHTML? We were watching the stack trace, let’s look at the code now.
Let’s first unassemble back from that point (function + offset). It’s easy, using the “ub” command in WinDbg.
0:030>ubEdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a: 8810693acallqwordptr[EdgeContent!_imp_SHCreateStreamOnFileEx(882562a8)] 88106940testeax,eax 88106942jnsEdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7d(8810695d) 88106944movrcx,qwordptr[rbp+18h] 88106948lear8,[EdgeContent!`string(88261320)] 8810694fmovr9d,eax 88106952movedx,1Fh 88106957callEdgeContent!wil::details::in1diag3::FailFast_Hr(8806597c) |
We will focus on the names only and ignore everything else, OK? Just like when we were trying to find a variation for the mimeType bug, we are going to speculate here and if we fail we would of course keep going deeper. But sometimes a quick look on the debugger can reveal many things.
We know that Edge will crash if it arrives to the last instruction of this snippet (address 88106957, FailFast_Hr). Our intention is to see why we ended up at that location, who the hell is sending us there. But let’s start from the beginning, the first instruction on this snippet seems to be calling a function with a complicated name which apparently reveals us tons of stuff.
EdgeContent!_imp_SHCreateStreamOnFileEx
The first part before the ! is the module (exe, dll, etc) where this instruction is located. In this case it is EdgeContent and we don’t even care about its extension, it’s just code. After the ! comes a funny name _imp_ and then SHCreateStreamOnFileEx which seems to be a function name that “creates a stream on file”. Do you agree? In fact, the _imp_ part makes me think that maybe this is an imported function loaded from a different binary. Let’s google that name to see if we find something interesting.
That’s pretty nice. The first result came with the exact name that we searched for. Let’s click on it.
OK. The first parameter that this function receives is a “A pointer to a null-terminated string that specifies the file name“. Interesting! If this snippet of code is being executed, then, it should be receiving a pointer to a file name as the first argument. But how can we see the first parameter? It’s easy, we are working on Winx64, and the calling convention / parameter passing says that “First 4 parameters – RCX, RDX, R8, R9” (speaking about integers/pointers). This means that the first parameter (pointer to a file name) will be loaded in the register RCX.
With this information, we can set a breakpoint before Edge calls that function and see what the RCX has at that precise moment. But let’s restart because it’s a bit late at this point: Edge already crashed, Please, re-do what’s described above (kill Edge, open it, load the page, find the process and attach).
This time, instead of running (F5) the process, we will set a breakpoint. The exact address of the instruction, we don’t know, but WinDbg revealed the exact offset, when we executed our “ub” command.
0:030>ubEdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a: 8810693aff1568f91400callqwordptr[EdgeContent!_imp_SHCreateStreamOnFileEx(882562a8)] 8810694085c0testeax,eax |
So the breakpoint should go in EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a
We type “bp” and the function name + offset [ENTER]. Then “g” to let Edge run.
0:029>bpEdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a 0:029>g |
Great. This is exciting. We want to see what’s the file name (or string) located in the register RCX right before SHCreateStreamOnFileEx executes. Let’s run the code and feel the break. Well, I feel it baby =) breakpoints connect me to my childhood. Let’s run the JavaScript code and bang! WinDbg breaks right there.
Breakpoint0hit EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a: 8820693aff1568f91400callqwordptr[EdgeContent!_imp_SHCreateStreamOnFileEx(883562a8)] |
That’s great, now we can inspect the content where RCX is pointing. To do this we will use the “d” command (display memory) @ and the register name, just like this:
0:030>d@rcx 02fac9087100770069006500-6900770071006500q.w.i.e.i.w.q.e. 02fac918690075003b006100-73006a0064006900i.u.;.a.s.j.d.i. 02fac9287700210040002300-240025005e002600w.!.@.#.$.%.^.&. 02fac9382a00000000000800-609ef802db010000*.......`....... 02fac94810a97002db010000-0100000000000000..p............. 02fac9580500000000000000-00000000196c0100.............l.. 02fac9684414003762de7746-9d6827f3e0920000D..7b.wF.h'..... 02fac9780000000000000800-0000000000000000................ |
This isn’t nice on my eyes but on the right of the first line I see something which looks similar to a unicode string. Let’s display again as unicode (du).
0:030>du@rcx 02fac908"qwieiwqeiu;asjdiw!@#$%^&*" |
Nice! The string rings me! Look at the JavaScript code that we’ve just ran.
It seems that the argument passed to this function is whatever we type after the comma. With this knowledge plus knowing that it is expecting a file, we can try a full path to something in my drive. Because Edge runs inside an AppContainer, we will try a file that’s accessible. For example something from the windows/system32 directory.
read:,c:\windows\system32\drivers\etc\hosts
We are also removing the garbage before the comma which seems unrelated (albeit it deserves more research!). Let’s quickly detach, restart Edge, and run our new code
url="read:,c:\\windows\\system32\\drivers\\etc\\hosts"; w=window.open(url,"","width=300,height=300"); |
And as expected, the local file loads in the new window without crashes.
[ PoC – Open hosts on MS Edge ]
Fellow bug hunter, I will stop here but I believe all these things deserve a bit more of research depending on what’s fun for you:
A) Enumerate all loadable protocols and attack those applications via query-strings.
B) Play with microsoft-edge: which bypasses the HTML5 sandbox, popup blocker and who knows what else.
C) Keep going with the read: protocol. We found a way to stop it from crashing but remember there is a function SHCreateStreamOnFileEx expecting things that we can influence! It’s worth trying more. Also, we can continue working on the arguments to see if commas are used to split arguments, etc. If debugging binaries is boring for you, then you can still try to XSS the reading view.
I hope you find tons of vulnerabilities! If you have questions, ping me at @magicmac2000.
Have a nice day!
Reported to MSRC on 2016-10-26
Reported to MSRC on 2016-10-26
To: secure@microsoft.com
Subject: MS Edge HTML5 Sandbox Escapes
Hey fellows! Here’s an Edge Sandbox escape.
http://www.cracking.com.ar/demos/sandboxedge
IMO, it’s a bad idea to let all those protocols run straight out of the box because the attack surface of Edge ends up being huge, but hey, it’s your browser! =)
Cheers!
Manuel.