导语:我的研究发现了Safari中的七个0-day漏洞(CVE-2020-3852,CVE-2020-3864,CVE-2020-3865,CVE-2020-3885,CVE-2020-3887,CVE-2020-9784和CVE- 2020-9787),其中三个用于构造利用链劫持访问相机。
0x01 漏洞背景
想象一下,当你在一个网站上看到一条消息有人劫持了你的相机和麦克风在监视你,这正是这一串漏洞可以做到的。
当在Desktop Safari(Mac)或Mobile Safari(iPhone或iPad)上查看时,此漏洞允许恶意网站伪装成受信任的网站。
然后,黑客可以使用其身份获取用户的隐私,之所以可行,是因为Apple允许用户在每个网站上永久保存其安全设置。
如果恶意网站想要访问摄像机,那么它所要做的就是伪装成可信任的视频会议网站,例如Skype或Zoom(Zoom刚爆出一堆漏洞,哈哈哈哈哈)。
我的研究发现了Safari中的七个0-day漏洞(CVE-2020-3852,CVE-2020-3864,CVE-2020-3865,CVE-2020-3885,CVE-2020-3887,CVE-2020-9784和CVE- 2020-9787),其中三个用于构造利用链劫持访问相机。
简而言之,该漏洞使Apple认为 恶意网站实际上是值得信赖的网站,它通过利用Safari如何解析URI,管理Web Origin以及初始化 Secure_Contexts的一系列缺陷来做到这一点。
如果恶意网站将这些漏洞利用串在一起,则可以使用JavaScript 直接访问受害者的网络摄像头,而无需征求许可。任何具有创建弹窗功能的JavaScript代码都可以发起此攻击。
我按照安全赏金计划规则向Apple报告了此漏洞,并使用BugPoC进行了实时演示。苹果公司认为此漏洞属于“ 无用户交互的网络攻击:对敏感Data的零点击未授权访问 ” 类别, 并奖励了我75,000美元。(只能膜拜的水平:()
0x02 漏洞描述
此项目的目标是入侵iOS / macOS网络摄像头,在这次挖掘中发现的其他漏洞仅仅是奖励漏洞。
在开始之前,我想先引用我的一位老同事的话:“ 挖掘漏洞就是为了在软件中找到输入,并违反那些输入看看会发生什么。” 这正是我要做的,我们将深入探索Safari的黑暗区域,并用奇怪的方法攻击浏览器, 直到发现奇怪的输出。
iOS和macOS中的相机安全模型非常严格,简而言之,必须为每个应用程序明确授予摄像头/麦克风许可,这由操作系统通过标准警报框处理。
但是这个规则有一个例外。苹果自己的应用程序可免费使用相机,因此,Mobile Safari从技术上无需询问即可访问相机。此外,诸如MediaDevices Web API(通常用于 WebRTC 传输)之类的新网络技术使网站可以利用Safari的许可直接访问摄像机。非常适合基于Web的视频会议应用程序,例如Skype或Zoom。但是...这项基于网络的新型摄像头技术破坏了操作系统的本机摄像头安全性模型。 这篇名为“ WebRTC安全性研究 ” 的论文很好地解释了这个难题:
https://webrtc-security.github.io
“如果用户选择他们知道可以信任的合适浏览器,那么所有WebRTC通信都可以被认为是'安全的'。换句话说,WebRTC提供给用户的信任级别直接受到用户信任的影响。”
因此,所有iOS / macOS用户都必须问自己一个问题,你对Safari的信任吗?
加密是WebRTC的强制功能,仅当你的网站处于“ Secure_Contexts ”中时,才公开mediaDevices API 来实施此操作。这意味着,即使像UXSS 这样的bug也无法访问摄像机,相机在Safari的内核中受到保护。
一些研究表明,Safari在每个网站的基础上会跟踪权限设置, 以使网站“无需总是请求权限”即可访问GPS位置或摄像头等敏感内容。基本上,由于你信任Skype,因此可以允许Skype随时访问你的相机,你可以在Safari>偏好设置>网站中查看当前信任的网站。
这项功能本质上要求Safari在Browser⟺Website上下文中从头开始重新实现OS⟺App安全模型。该模型提出了一个问题-Safari对这些网站的跟踪情况如何?
我们开始制定攻击计划:如果我们可以以某种方式诱使Safari认为我们的恶意网站位于受信任网站的“安全上下文”中,则可以利用Safari的摄像头许可通过mediaDevices API访问网络摄像头。
0x03 漏洞分析
为了使Safari使用你的网站设置,它当然需要知道你当前正在查看哪些网站。这实际上是所有浏览器的基本功能,并且是维护Same-Origin-Policy的核心,试图欺骗浏览器使其不承担责任是UXSS或SOP绕过攻击的关键。
但是在看了一会之后,我注意到了一件奇怪的事情,Safari似乎根本没有使用原始记录来跟踪“当前打开的网站”。实际上,他们在此处跟踪网站的方法非常奇怪。
在上面的示例中,所有4个浏览器窗口都有唯一的Origin。但是,Safari似乎只认为1个网站已打开,经过更多的实验后,我推断Safari可能针对所有打开的窗口运行通用URI语法解析器,以获取URI的主机名,然后对它们进行一些额外的解析。具体来说,Safari似乎在主机名的开头删除了“ www”。
在做了一些简短的主机名模糊处理之后,我注意到了一个奇怪的bug。Safari的“当前打开的网站”功能完全看不见带有破折号(-)和点号(。)的主机名(即“-。”或“ .-”)。我无法立即弄清楚如何使用这个bug去摄影机,有点不安的是,该菜单中缺少“ https://foo-.evil.com”。BugPoC上的漏洞演示:bugpoc.com/poc#bp-Ay2ea1QE 密码layMouse24,该演示仅在使用Safari 13.0.4进行浏览时才有效。
一个更重要的发现是URL的方案被完全忽略了。这是有问题的,因为某些方案根本不包含有意义的主机名,例如file:,javascript:或data:。其他方案将主机名包含在嵌入式URI中,例如view-source:或blob:。Safari不能正确地获取“://”和“ /”之间的字符串并将其视为有效的主机名。Safari应该保留了可以像这样解析的方案的白名单,例如http:,https:,ws:等(CVE-2020-3852)
现在是时候找到一种方法来利用这种漏洞的假设了,我使用了一些伪协议。
此处的目标是创建一个URI,当使用RFC 3986中定义的“ 通用URI语法”进行解析时,它会生成受害者信任的任意主机名。
javascript://skype.com 应该可以解决问题,对不对?Safari在这种情况下看到的主机名应该是 skype.com。事实证明,当Safari尝试打开此URL时,页面实际上加载了“ about:blank ”,并且内容直接发送到JavaScript引擎,而无需点击URL栏。我尝试了几种方法来停止页面加载并欺骗Safari给我提供此href,但是都没有成功。
接下来是Data:目标是创建一个同时由RFC 2397(Data:)和RFC 3986 解析的URI。如果你愿意,可以使用Polyglot URL,经过一些测试,我想到了:data://@skype.com。我使用标准window.open()打开了此页面,并检查了Safari偏好设置,Safari认为skype.com 目前处于打开状态。
成功了!
但是我有一个问题,尽管从技术上讲是有效的Data:URI,但mimetype是“ //”,Safari无法识别它,并且该规范规定默认的mimetype是 text / plain。这意味着“ data://,@skype.com?
'); > SecurityError
实际上,开发这种隔离的原始保护是为了防止新文档与其父文档混为一谈,反之亦然。但是从技术上讲,它也可以阻止此类攻击。我尝试了几种不同的方法来诱使Safari呈现我的文本/纯文本Data:URI作为HTML,但没有一个成功。
在仔细研究了Safari内部如何解析此URL之后,我决定尝试使用window.history。也许我可以从HTMLData开始:URI,然后将“路径名”更改为“ //skype.com”,而无需进行任何实际的页面加载或导航(因此也无需更新模仿类型)。
不幸的是,RFC(sort-of)看到了这种情况的出现,并明确禁止使用history.pushState或history.replaceState更改origin。注意,该规范引用了“origin”的真实算法定义,而不是经典的方案/主机/端口元组。在这种情况下(Data:URI)做一个history.replaceState将从原点改变origin于不透明基点B。
history.replaceState('','','data://skype.com') > SecurityError
about:
我的候选清单上的最后一个是about:,这个似乎会有效果。,Safari实际接受about://skype.com!
w = open('about://skype.com'); w.document.write('<script> alert(1)</ script>'); > SecurityError
似乎Safari仅允许about:blank和about:srcdoc 继承opener的Origin。我发现了一个旧的WebKit漏洞报告,他们在其中考虑放宽此限制,但没有尝试。目前,about://skype.com具有唯一的不透明Origin,就像data:一样。这也意味着h istory.pushState 恶意攻击也将被阻止。
file:我使用的下一个方案是file:。该方案不包含有意义的主机名,我深入探究RFC,实际上偶然发现了文件的一个变化:](https://tools.ietf.org/html/rfc8089#appendix-E.3.1)确实包含主机名的URI。这种URI实际上指定了一个远程服务器,类似于FTP,但是此规范未定义对存储在远程计算机上的文件的检索机制,搜索了一段时间后,我找不到任何实际上支持这种的URI类型的用户代理。
file://host.example.com/Share/path/to/file.txt
出于好奇,我检查了Safari如何在内部解析普通文件URI。
如预期的那样,主机名为空。我决定继续使用JavaScript指定主机,以查看会发生什么。
我勒个去,该页面实际上将该URI视为有效,并重新加载了相同的内容。这意味着我只是使用这个愚蠢的技巧更改了document.domain。(CVE-2020-3885)
果然,Safari认为我们在skype.com上 ,我可以加载一些邪恶的JavaScript。当你打开本地HTML文件时,相机,麦克风和屏幕共享都会受到损害。Safari似乎也使用这种惰性主机名解析方法来填充密码的自动完成功能,因此,如果你接受自动完成功能,我可以窃取纯文本密码。
file://exploit.html
< script > if (location.host != 'google.com'){ location.host = 'google.com'; } else { alert(document.domain); } < /script >
此攻击需要受害者打开本地HTML文件。另外,它在iOS上不起作用,因为通过Mobile Safari下载的本地文件在没有JavaScript引擎的情况下以预览样式的嵌入式视图显示。
0x04 自动下载漏洞
为了使上述file://攻击更现实,我决定研究如何诱使Safari从我们的网站自动下载恶意HTML文件。(当然,下载文件只是成功的一半,受害者仍然需要打开它)
我记得前段时间阅读过有关Edge在Broken Browser上的一个简单的头欺骗漏洞的信息。事实证明,可以使用类似的技术轻松绕过Safari中的自动下载防护!只需在弹出窗口中打开受信任的网站,然后将弹出窗口重新用于下载链接即可。(CVE-2020-9784和CVE-2020-3887)
open(' https: //dropbox.com','foo'); setTimeout(function(){open('/ file.exe','foo');},1000)
我使用BugPoC Mock Endpoint功能制作了一个带有Content-Disposition 附件响应标头的URL,该标头包含一个用于演示的小文本文件。BugPoC演示:bugpoc.com/poc#bp-t9C660OJ密码quietOkapi20。 请注意,该演示仅在使用Safari 13.0.4进行浏览时才有效。
0x05 怪异的blob:
此BLOB: 中封装伪协议有一个有趣的工具。它使你可以使用随机标识符直接访问隐藏在浏览器内存中的文件,这样一来,你就可以轻松引用动态创建的文件。这些类型的URI通常用于图像和视频,但是一个有趣的功能是它们实际上允许你自己指定模仿类型。Safari甚至会尽力呈现所有可能的内容,因此你可以制作HTML文档并在新标签页中打开它。
blob = new Blob(['hello, world!'], {type: 'text/html'}); url = URL.createObjectURL(blob); open(url,'_blank');
Blob:规范规定了浏览器生成这些纯净URI必须使用的算法。
blob: [origin of creating document] / [random UUID]
但是算法中有一些细微之处值得一一列举,原点被序列化为字符串。但是,如果构成Blob URI的Origin没有有意义的序列化,会发生什么?就像一个不透明的有效域文档,其Origin不透明。快速提醒一下,不透明的Origin(例如给data:text / html,foo 或about:// foo的Origin)总是被序列化为字符串“ null ”,并且这种序列化是1向的。这意味着,在内部,Safari将data:text / html,foo和data:text / html,bar视为具有两个唯一Origin,即使它们都变为“ null””。
“ 如果序列化为'null',则将其设置为实现定义的值。”
在规格上一定会产生歧义。让我们看看Safari如何处理这种奇怪的情况,我们可以从data:URI开始给一个不透明的Origin data:text / html,foobar,然后创建一个blob:URI。
因此,blob:URI中的序列化Origin实际上是字符串“ null ”,就像在控制台上打印时一样。因为按照定义,这种序列化是1向方式,所以我很好奇Safari如何知道允许哪个不透明Origin打开URI。
经过一些试验,我确定Safari实际上在此处强制执行SOP,因此它必须使用随机UUID来帮助找到真正的创建Origin。使用创建文档中的JavaScript来打开该blob URI可以按预期工作,并且新文档能够继承RFC神一直想要的不透明Origin。尝试使用JavaScript从其他不透明Origin打开此URI 也会导致漏洞,也与预期的一样。
在Safari地址栏中手动输入此URL可以使我知道原点“ :// ”
这似乎是Safari认为适合给我的“ 空白 ”Origin。这不是创建此文档的不透明Origin。实际上,根据原点序列化标准,这根本不是一个不透明的原点。这是通过算法运行空白方案,空白主机和空白端口的结果。(CVE-2020-3864)我将把这个离奇的起Origin称为“空白”Origin。经过更多的试验之后,我发现此null-blob-URI继承了任何开启程序的Origin!但是要注意的是,Safari会检查以确保允许打开程序首先打开URL(从而保持SOP)。但是,当打开的文件不是普通文件时,这很麻烦。BugPoC上的工作演示:bugpoc.com/poc#bp-wkIedjRe密码laidFrog49。该演示仅在使用Safari 13.0.4进行浏览时才有效。
从书签访问此URI会导致某些异常行为。
你可以看到Safari尝试向该null-blob-URI授予https://example.comOrigin,但是失败了,因为它实际上没有在该内存位置存储任何文档(因此出现WebKitBlobResource漏洞)。
但是,当打开器的开头为“空白”://时,Safari显然能够找到该文档。我偶然发现了一个奇怪的Origin,在某些情况下它等于null,但在技术上并不透明。我只需要在地址栏中手动输入null-blob-URI 即可到达那里。
因此,现在我们需要找到一种方法,以编程方式获得这种神奇的“空白”Origin。基本上只是一种模拟手动地址栏条目的方法。幸运的是有一个API!该location.replace API替换当前的资Origin,如果它是最初导航到的URL。但是请记住,Safari会检查以确保你具有查看此URL的权限,因此该替换位置必须来自创建该URL的同一文档。
从不透明的origin开始:
blob = new Blob(['hello, world!'], {type: 'text/html'}); url = URL.createObjectURL(blob); location.replace(url);
我们成功地将Data从不透明Origin的URI跳到了blob:Origin空白的 URI 。
0x06 漏洞利用链
回到我们尝试使用window.history处理Data:URI时,我们失败了,因为更改 路径名也 无意中更改了不透明的起Origin(历史规范明确禁止的行为),我们看看现在不再处于不透明的原点上是否有所不同。
提醒我们此时的情况:
让我们尝试一些非常激进的方法:
history.pushState('','','blob://skype.com'); > SecurityError
似乎Safari正确认识到上述pushState会将我们发送到新的Origin,但是后来我发现了一些非常奇怪的东西,下面pushStates 是不允许的!
history.pushState('','','blob://'); history.pushState('','',kype .com'); location.href > blob://skype.com
这里发生了什么?为什么当我们一次尝试时就不允许这种行为,而当我们将其分解时却完全没问题?好了,确定blob的步骤:URI的Origin可以在这里找到。我最好的猜测是Safari正确地认为blob://skype.com的起Origin 是一个新的不透明Origin(规范中的第3步),但出于某种原因,它认为blob://的Origin 是:// 或我们拥有的被称为“空白”Origin(规范中的第2步)。(已修复为CVE-2020-3864的一部分)
因为我们当前的原点也是://,所以允许该pushState。下一个pushState只是更改路径名,因此Safari不会看到它的问题。而这样的,我们现在有location.href的://skype.com!快速浏览Safari偏好设置会将skype.com显示 为当前打开的网站。
尽管我们确实在Safari识别为skype.com的文档中执行了JavaScript,但它不是“安全上下文”,因此我们 没有像mediaDevices这样的有趣API。
果然...
但是,我们可以执行自动下载,自动弹出和自动完成的纯文本密码,让我们继续努力。
让我们仔细看看“ 安全上下文 ” 到底是什么。
“安全上下文是一个窗口,可以合理确信该内容或内容已通过HTTPS / TLS安全地传输,并且与不安全的上下文进行通信的可能性受到限制。许多Web API和功能只能在安全的上下文中访问。安全上下文的主要目标是防止中间人攻击者访问强大的API,这些API可能进一步危害攻击的受害者。”
好的,这是使用WebRTC的可理解的要求。如果你的WiFi上的任何人都可以访问你的网络摄像头(假设你访问的是以前信任的HTTP网站),那将非常可怕,现在是时候找到一个新的漏洞来规避此要求。
在深入研究Secure Context 规范后,我注意到了一个矛盾:浏览器也被允许将文件:URL视为可信任的,因为它“ 对于开发人员在将应用程序部署到公众之前构建应用程序来说很方便”。
我很好奇Safari如何实现此异常,因此我开始探索是什么使文件:URL唯一。围绕该协议的SOP规则已经引起了激烈的争论,这些URL的Origin取决于浏览器。Safari的现代版本为每个文件提供了一个 独立的 不透明Origin,经过 一些试验,我发现Safari将所有具有不透明Origin的文档视为安全上下文。(CVE-2020-3865)这是一个非常大的疏忽,因为HTTP站点很容易创建不透明的原始文档。
这里唯一的问题是规范说“ 为了使页面具有安全的上下文,页面及其父级和开放者链中的所有页面都必须安全交付。”,这意味着嵌入在HTTP网站中的不透明原始文档最终被认为是不安全的。
对于我们来说幸运的是,Safari似乎忽略了规范的“开放链”部分,而仅检查父母的安全性。这样一来,我们 只需从沙盒iframe中打开一个弹出窗口即可创建一个安全的上下文窗口。
侧边栏-MiTM攻击者为HTTP网站提供不透明Origin的另一种可能更容易的方法是,将CSP沙箱标头添加到响应中。
但这对blob://skype.com世界有何帮助?该URL是伪造的,因为它从未通过Internet传递给我们。执行此sandboxed-iframe-popup技巧对我们而言不起作用,因为执行window.open('/')将使Safari尝试真正加载blob://skype.com……
因此,我们需要考虑一种使用以下方式打开弹出窗口的方法: 1) blob://skype.com URI,2)不透明的Origin,3)任意JavaScript。
我记得在Broken Browser 上读过的文章,当继承的原始文档执行document.write()时,Edge会崩溃。
事实证明,在Safari中,如果文档执行document.write到继承的原始文档,则文档实际上会产生location.href。
这样就完成了我们在此处尝试执行的第1部分和第3部分。现在,我们可以使用blob://skype.com URI和任意JavaScript 弹出窗口 ,现在我们只需要弄清楚如何给它一个不透明的原点即可。
我们现在的情况
这里最棘手的部分是,仅当弹出窗口与我们的Origin相同(遵守SOP)时,才允许document.write()。因此,我们需要以某种方式执行document.write(),然后将其Origin归零。
让我们从空白的blob://skype.com URI开始,并创建一个常规的iframe到about:blank。然后执行document.write()来为该iframe提供 blob://skype.com href。然后,我们将sandbox属性动态添加到此iframe并从以前开始执行sandboxed-iframe-popup技巧。 如果我们做对的话,这应该从父-> iframe- >弹出窗口传播 blob://skype.com href。
但是我们的计划存在一个问题-iframe 规范说,仅在iframe导航到新页面后才能应用动态添加的沙盒标志。
此时我们的URL是伪造的。进行任何框架导航都会打破这种错觉,并要求Safari真正尝试获取/加载。即使诸如location.reload()之类的无害内容, 也会使Safari意识到它位于伪造的 URL上并产生漏洞。
因此,我们需要提出一种无需Safari实际更改URL或页面内容即可强制进行框架导航的方法。
可是如果导航由于我们无法控制而失败了怎么办?如果Safari确实尝试进行获取/加载但无法完成获取/加载,该怎么办。如果我们尝试在响应中使用X-Frame-Options标头将iframe导航到真实网址,该怎么办?
document.getElementById('theiframe').contentWindow.location = 'https://google.com'; > Refused to display 'https://www.google.com/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'.
现在应用了动态添加的沙箱标志,但未更改iframe URL和内容。
document.getElementById('theiframe').contentDocument > Sandbox access violation: Blocked a frame at "://" from accessing a frame at "null".
现在,我们有了一个有blob://skype.com href和任意JavaScript内容的沙盒iframe 。一个简单的window.open()弹出窗口是最后一步。BugPoC演示:bugpoc.com/poc#bp-2ONzjAW6 密码blatantAnt90。该演示仅在使用Safari 13.0.4进行浏览时才有效。
我们终于做到了。我们从一个普通的HTTP网站开始,然后到一个安全上下文中一个混混的Blob URI,这是我们如何做到的总结:
1.打开恶意的HTTP网站
2.HTTP网站成为Data:URI
3.Data:URI变成blob:URI
4.操作 window.history
5.创建一个about:blank iframe和document.write
6.动态给此iframe 沙盒属性
7.尝试使用X-Frame-Options进行不可能的帧导航
8.在iframe中,window.open弹出一个新的窗口并document.write到它
9.利用成功
在此弹出窗口中,我们可以使用mediaDevices Web API来访问网络摄像头(前后),麦克风,屏幕共享(仅macOS)等等!要在弹出窗口中获取“恶意代码”(mediaDevices JavaScript),我们需要玩疯狂的土豆游戏。
这是最终图:
最后,录屏记录了这种攻击的样子:
预先录制的演示中的受害者先前信任skype.com
BugPoC上的演示:bugpoc.com/poc#bp-HHAQuUYC密码:blahWrasse59。 该演示仅在使用Safari 13.0.4进行浏览时才有效。
本文翻译自:https://www.ryanpickren.com/webcam-hacking如若转载,请注明原文地址