XSS-1-从XSS开始
xss篇均转译自https://aszx87410.github.io/
浏览器安全模型
在探讨网页前端安全问题时,我们必须首先对网页前端的核心有基本认知。
网页前端最大的不同在于代码在浏览器上运行。浏览器负责渲染HTML、解析CSS以及执行页面上的JavaScript代码。
对于Web前端而言,其运行环境就是浏览器。
牢记这一点对于理解网页前端为何存在功能限制至关重要。并非我不想实现,而是浏览器不允许我这样做。例如,后端服务器能轻松执行文件读写操作,但在网页前端可能无法实现。为什么?因为浏览器不允许我们这样做。
为何我能看到他人实现关于____(填入任意内容)的功能,却找不到前端实现的方法?这很可能是因为浏览器不允许你这么做。
简而言之:如果浏览器不提供,你就无法获取。真的无法获取。
那么浏览器施加了哪些安全限制?它限制了什么?以下是一些示例。
禁止主动访问本地文件
对于后端而言,其代码直接在操作系统上运行,这意味着它本质上就是一个普通应用程序。若无特殊权限限制,它几乎可以为所欲为——整台机器都是它的游乐场。
但前端则面临诸多限制。例如它无法主动读写计算机文件。让我们谈谈可行的方案:可通过 <input type=file> 元素让用户选择文件,再借助FileReader接口读取文件内容,如下所示:
1 | <input type="file" onchange="show(this)"> |
但你不能直接使用诸如读取文件之类的操作。若尝试这样做,控制台只会显示错误信息:fetch('file:///data/index.html')
不允许加载本地资源:
file:///data/index.html
即使使用 window.open('file:///data/index.html') 也会导致相同错误。
浏览器设置限制存在绝对必要性。试想若前端网页能直接读取文件会怎样?我能直接读取你的 /etc/passwd 文件,读取你的SSH密钥,读取配置文件及各类包含敏感信息的文件。甚至能找到你电脑上加密加密货币钱包的备份助记词。这将引发重大安全隐患,如同遭受恶意软件入侵。
因此禁止JavaScript主动访问文件是完全合理的。否则仅需打开网页,所有文件内容便会暴露,引发严重安全隐患。
事实上此类事件早有先例。让我们回顾一则案例。
2021年,Renwa向Opera报告了漏洞:Bug Bounty Guest Post: Local File Read via Stored XSS in The Opera Browser,该漏洞利用浏览器缺陷实现文件读取。
Opera基于Chromium开发的浏览器,其”Opera Pinboards”功能允许用户创建笔记并分享。笔记页面的URL采用特殊协议opera:pinboards,通常享有特殊权限。
创建笔记时可添加链接,例如:https://blog.huli.tw。Renwa发现除常规链接外,使用 javascript:alert(1) 此类链接也能执行代码,从而在 opera:pinboards 下形成XSS漏洞。
如前所述,opera: 具有特殊权限,例如可打开 file:// 网页并截取网页截图以获取截图结果。因此,可利用前述 XSS 漏洞打开本地文件、截取截图并发送至攻击者服务器,实现窃取文件的目的。
该漏洞在报告后一天内被修复,报告者获得了 4000 美元的奖励。
禁止呼叫系统 API
常规应用程序可通过系统提供的API执行多种操作,例如修改系统设置或网络配置。然而JavaScript无法实现这些功能。
更准确地说,并非JavaScript本身不具备能力——它只是编程语言本身。根本原因在于”浏览器未向网页前端提供对应的API接口,因此无法实现”。
在网页前端执行JavaScript时,我们只能使用浏览器提供的功能。例如通过 fetch() 发送请求,或使用 setTimeout 设置定时器——这些都是浏览器提供的接口,允许我们执行特定操作。
若需调用系统API,必须浏览器同时提供对应接口。否则网页端的JavaScript无法访问这些功能。
例如浏览器提供的Web Bluetooth API用于与蓝牙设备通信,因此网页上的JavaScript可用于开发蓝牙相关应用;MediaDevices API则允许JavaScript访问麦克风、摄像头等设备数据,从而开发相关应用。
当浏览器提供这些API时,通常会同步实现权限管理机制。在允许网页访问特定资源前,系统会弹出通知提示用户主动同意并授予权限。
禁止存取其他网页的内容
这可视为浏览器最重要的安全假设之一。网页绝不应被允许访问其他网页的内容。此规则易于理解:若允许此类操作,攻击者便可通过访问 mail.google.com 直接读取 blog.huli.tw 的邮件内容,这显然存在安全隐患。
因此每个网页仅拥有自身权限:可修改自身HTML内容并执行所需JavaScript代码,但不得访问其他网页的数据。此机制即为同源策略(SOP)。
需要强调的是,此处”数据”不仅限于”页面内容”,更包含无法获取”其他页面的URL地址”这一限制。
例如,如果在 github.com 上执行以下代码:
1 | var win = window.open('https://blog.huli.tw') |
将显示以下错误信息:
该消息表明:
未捕获的 DOMException:阻止了源自 “https://github.com“ 的帧访问跨源帧。
这意味着您无法访问其他页面的内容,包括其网址。
尽管这看似基础且必要,但在浏览器中实现该功能并非易事。浏览器经历了无数次攻击,通过实施各种防御措施和架构调整,不断提升安全性以满足这些要求。
例如,2018年1月谷歌Project Zero披露了名为Meltdown和Spectre的重大漏洞,攻击者可通过CPU缺陷读取同一进程中的数据。
Chrome浏览器通过架构调整增强安全性来应对该漏洞,确保不同网页(无论通过何种方式加载,包括图片和iframe)均在独立进程中处理。这一系列安全措施被称为”站点隔离”,Chromium官网有更详细的说明,后续文章也将再次提及。
关于”无法访问其他页面的内容”这一限制,我们来看一个绕过该限制的示例。
2022年,joaxcar向Chromium报告了一个漏洞:Issue 1359122:安全漏洞:当位置更改为about:blank时,SOP绕过机制会泄露来自其他子域的iframe导航历史记录。该漏洞允许通过iframe读取跨域URL。
假设网页为 a.example.com,其中嵌入一个URL为 b.example.com 的iframe。通过使用 frames[0].location = 'about:blank' 将iframe重定向之后,该iframe便与 a.example.com 成为同源。此时通过访问 iframe 的导航历史记录: frames[0].navigation.entries() 就可以从里面拿到原本 b.example.com 的网址。
此现象不应发生。当iframe被重定向至其他URL时,导航历史记录应被清除。因此这属于漏洞。
此为绕过同源策略的示例。尽管仅允许读取URL,仍构成安全漏洞,该漏洞被发现后获得了2000美元奖励。
小结
本文的核心观点在于”若浏览器未提供,则无法获取”。这正是网页前端开发与其他执行环境的关键差异。反之,若能获取浏览器未提供的内容,则意味着发现了浏览器漏洞,可据此申请漏洞赏金。
那么最严重的浏览器漏洞是什么?正是允许攻击者绕过浏览器限制,执行违反浏览器安全假设的操作。
例如前文提到的SOP绕过漏洞,就能破坏同源策略并访问其他网页的数据。虽然前例仅演示了读取URL的能力,但更复杂的攻击甚至能获取内容本身。试想:当你打开我的博客文章 https://blog.huli.tw 阅读时,我的网站却在后台秘密执行JavaScript代码,利用SOP绕过漏洞读取你 https://mail.google.com 邮箱中的全部内容。
听起来很可怕,对吧?但更可怕的还在后头。
最严重的漏洞类型允许攻击者利用JavaScript在计算机上执行任意命令,这种漏洞被称为远程代码执行(RCE)。
再举个例子。假设你访问我的博客阅读文章后关闭页面,此时我已能向你的计算机发送指令——窃取所有数据或秘密植入恶意软件。历史上此类漏洞被利用的案例屡见不鲜,浏览器也时常暴露这类最严重的漏洞。作为普通用户,我们最好的应对方式是及时更新浏览器以最大限度降低风险。
2021年9月,研究人员发现编号为CVE-2021-30632的漏洞,正是前述RCE漏洞。攻击者只需诱使用户通过Chrome浏览器(v93之前版本)打开特定网页,便能直接入侵计算机执行任意指令。
你是否好奇此类攻击中JavaScript代码的典型形态?攻击者如何利用特定功能,最终通过浏览器执行任意代码?
以下是CVE-2021-30632漏洞的一个利用程序,来源:https://github.com/CrackerCat/CVE-2021-30632/blob/main/CVE-2021-30632.html
1 | <!DOCTYPE html> |
由于该漏洞存在于V8引擎内部,您会发现上述代码执行了许多看似难以理解的操作。这些操作通常旨在满足特定条件以触发V8引擎中的问题。不过详细解释已超出本文范围,感兴趣者可参考GitHub安全团队撰写的深度分析:Chrome在野漏洞分析:CVE-2021-30632
顺带一提,部分不熟悉JavaScript限制的工程师常试图用JavaScript实现根本不可能完成的任务。
当您理解浏览器的基本安全模型后,面对无法实现的任务时,便能自信地告知项目经理:”此功能无法在网页前端实现,因为浏览器不支持该操作”,而非四处寻找调用不存在API的方法。
