文章预览:
浏览器安全可以分为三大块——Web 页面安全、浏览器网络安全和浏览器系统安全,01-03介绍页面安全,04和05分别介绍网络安全和系统安全。
01 同源策略:为什么XMLHttpRequest不能跨域请求资源?
什么是同源:如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。同源策略主要表现在 DOM、Web 数据和网络这三个层面:
- DOM层面:不同源的JavaScript脚本不能对当前DOM对象进行读和写的操作
- 数据层面,无法读取Cookie、IndexDB、LocalStorage等数据
- 网络层面,不能通过XMLHttpRequest 等方式访问跨域的资源
不过鱼和熊掌不可兼得,要绝对的安全就要牺牲掉便利性,因此我们要在这二者之间做权衡,找到中间的一个平衡点,也就是目前的页面安全策略原型(同源随便瞎搞,不同源瞎搞要通过浏览器提供的手段)。总结起来,它具备以下三个特点:
- 页面中可以引用第三方资源,不过这也暴露了很多诸如 XSS 的安全问题,因此又在这种开放的基础之上引入了内容安全策略 CSP 来限制其自由程度。
- 使用 XMLHttpRequest 和 Fetch 都是无法直接进行跨域请求的,因此浏览器又在这种严格策略的基础之上引入了跨域资源共享策略CORS,让其可以安全地进行跨域操作。
- 两个不同源的 DOM 是不能相互操纵的,因此,浏览器中又实现了跨文档消息机制,让其可以比较安全地通信。
02 跨站脚本攻击(XSS):为什么Cookie中有HttpOnly属性?
2.1 什么是XSS攻击
XSS 全称是 Cross Site Scripting,即跨站脚本。XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。
当页面被注入了恶意 JavaScript 脚本时,浏览器无法区分这些脚本是被恶意注入的还是正常的页面内容,所以恶意注入 JavaScript 脚本也拥有所有的脚本权限:
- 窃取Cookie信息:恶意 JavaScript 可以通过“document.cookie”获取 Cookie 信息然后发送到恶意服务器,进而模拟用户的登录
- 监听用户的行为:如使用“addEventListener”接口来监听键盘事件获取用户的输入
- 修改DOM伪造假的登录窗口
- 在页面生成浮窗广告,等等
2.2 恶意脚本常见的注入方式
(1)存储型XSS攻击:将恶意代码提交到网站的数据库中,用户请求了包含恶意脚本的页面并浏览时,恶意脚本就会将用户的 Cookie 信息等数据上传到服务器。
(2)反射型XSS攻击:在一个反射型 XSS 攻击过程中,恶意 JavaScript 脚本属于用户发送给网站请求中的一部分,随后网站又把恶意 JavaScript 脚本返回给用户。
如服务器有代码如下:
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express',xss:req.query.xss });
});
如果请求的参数中xss等于一段恶意脚本,服务器接收到请求时就会将恶意代码反射给浏览器端,黑客经常会通过 QQ 群或者邮件等渠道诱导用户去点击恶意链接来发送带恶意脚本的请求。
(3)基于 DOM 的 XSS 攻击:基于 DOM 的 XSS 攻击是不牵涉到页面 Web 服务器的。具体来讲,黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改 HTML 页面的内容,这种劫持类型很多,有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据。
2.3 如何阻止 XSS 攻击
(1)服务器对输入脚本进行过滤或转码,例如过滤掉
(2)充分利用CSP:例如限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个 JavaScript 文件也是无法被加载的;禁止向第三方域提交数据,这样用户数据也不会外泄;禁止执行内联脚本和未授权的脚本;提供上报机制,这样可以帮助我们尽快发现有哪些 XSS 攻击,以便尽快修复问题。
(3)使用HttpOnly属性:在set-cookie 属性值最后使用HttpOnly 来标记一个 Cookie,这样这个Cookie 就只能使用在 HTTP 请求过程中,无法通过 JavaScript 来读取
03 CSRF攻击:陌生链接不要随便点
3.1 什么是CSRF攻击
CSRF 全称是 Cross-site request forgery,即“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事。
如David域名的被盗流程:
要发起CSRF攻击需要具备三个条件:
- 目标站点一定要有 CSRF 漏洞;
- 用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态;
- 需要用户打开一个第三方站点,可以是黑客的站点,也可以是一些论坛。
满足以上三个条件之后,黑客就可以通过自动发起Get请求、自动发起POST请求,引诱用户点击链接等方式对用户进行 CSRF 攻击了。
3.2 如何阻止CSRF攻击
与 XSS 攻击不同,CSRF 攻击不会往页面注入恶意脚本,因此黑客是无法通过 CSRF 攻击来获取用户页面数据的;其最关键的一点是要能找到服务器的漏洞,所以说对于 CSRF 攻击我们主要的防护手段是提升服务器的安全性:
(1)充分利用好 Cookie 的 SameSite 属性
黑客会利用用户的登录状态来发起 CSRF 攻击,而 Cookie 正是浏览器和服务器之间维护登录状态的一个关键数据,因此要阻止 CSRF 攻击,我们首先就要考虑在 Cookie 上来做文章:
- 如果是从第三方站点发起的请求,那么需要浏览器禁止发送某些关键 Cookie 数据到服务器;
- 如果是同一个站点发起的请求,那么就需要保证 Cookie 数据正常发送。
因此在通过 set-cookie 字段设置 Cookie 时,可以带上 SameSite 选项,SameSite 选项通常有 Strict(最为严格,完全禁止第三方Cookie)、Lax(相对宽松,从第三方站点的链接打开和从第三方站点提交 Get 方式的表单会携带) 和 None(任何情况下都会发送Cookie数据) 三个值
(2)验证请求的来源站点
利用HTTP 请求头中的 Referer(记录了一个 HTTP 请求的来源地址) 和 Origin(只包含域名信息,不包含具体的URL路径) 属性
(3)CSRF Token
在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中。
<!DOCTYPE html>
<html>
<body>
<form action="https://time.geekbang.org/sendcoin" method="POST">
<input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
<input type="text" name="user">
<input type="text" name="number">
<input type="submit">
</form>
</body>
</html>
那么在浏览器端如果要发起转账的请求,则需要带上页面中的 CSRF Token,然后服务器会验证该 Token 是否合法。如果是从第三方站点发出的请求,那么将无法获取到 CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求。
综合01-03,我们可以得出页面安全问题的主要原因就是浏览器为同源策略开的两个“后门”:一个是在页面中可以任意引用第三方资源,另外一个是通过 CORS 策略让 XMLHttpRequest 和 Fetch 去跨域请求资源。为了解决这些问题,我们引入了 CSP 来限制页面任意引入外部资源,引入了 HttpOnly 机制来禁止 XMLHttpRequest 或者 Fetch 发送一些关键 Cookie,引入了 SameSite 和 Origin 来防止 CSRF 攻击。
04 安全沙箱:页面和系统之间的隔离墙
浏览器本身的漏洞是单进程浏览器的一个主要问题,如果浏览器存在漏洞,那么黑客就有机会通过页面对系统发起攻击。
因此在设计现代浏览器的体系架构时,就考虑到这个问题了。于是,在多进程的基础之上引入了安全沙箱,有了安全沙箱,就可以将操作系统和渲染进程进行隔离,这样即便渲染进程由于漏洞被攻击,也不会影响到操作系统的。
由于渲染进程采用了安全沙箱,所以在渲染进程内部不能与操作系统直接交互,于是就在浏览器内核中实现了持久存储、网络访问和用户交互等一系列与操作系统交互的功能,然后通过 IPC 和渲染进程进行交互。
Chrome 中最新的站点隔离功能:由于最初都是按照标签页来划分渲染进程的,所以如果一个标签页里面有多个不同源的 iframe,那么这些 iframe 也会被分配到同一个渲染进程中,这样就很容易让黑客通过 iframe 来攻击当前渲染进程。而站点隔离会将不同源的 iframe 分配到不同的渲染进程中,这样即使黑客攻击恶意 iframe 的渲染进程,也不会影响到其他渲染进程的。
05 HTTPS:让数据传输更安全
由于 HTTP 的明文传输特性,在传输过程中的每一个环节,数据都有可能被窃取或者篡改(中间人攻击),这倒逼着我们需要引入加密机制。于是我们在 HTTP 协议栈的 TCP 和 HTTP 层之间插入了一个安全层,负责数据的加密和解密操作。
5.1 对称加密
对称加密是指加密和解密都使用的是相同的密钥。
具体过程如下:
- 浏览器发送它所支持的加密套件列表和一个随机数 client-random,这里的加密套件是指加密的方法,加密套件列表就是指浏览器能支持多少种加密方法列表。
- 服务器会从加密套件列表中选取一个加密套件,然后还会生成一个随机数 service-random,并将 service-random 和加密套件列表返回给浏览器。
- 最后浏览器和服务器分别返回确认消息。
这样浏览器端和服务器端都有相同的 client-random 和 service-random 了,然后它们再使用相同的方法将 client-random 和 service-random 混合起来生成一个密钥 master secret,有了密钥 master secret 和加密套件之后,双方就可以进行数据的加密传输了。
但是由于传输过程是明文的,这意味着黑客也可以拿到协商的加密套件和双方的随机数来合成密钥。
5.2 非对称传输
服务器会将公钥通过明文的形式发送给浏览器,自己留下私钥,不对任何人公开。在浏览器端向服务器端发送数据时,就可以使用公钥来加密数据。由于公钥加密的数据只有私钥才能解密,所以即便黑客截获了数据和公钥,他也是无法使用公钥来解密数据的。
不过无法保证服务器发送给浏览器的数据安全,因为黑客还是可以获取公钥,而且非对称加密的效率很低。
5.3 对称加密和非对称加密搭配使用
采用对称加密的方式加密传输数据和非对称加密的方式传输密钥。
其中,pre-master 是经过公钥加密之后传输的,所以黑客无法获取到 pre-master,这样黑客就无法生成密钥,也就保证了黑客无法破解传输过程中的数据了。
采用这种方式虽然能保证数据的安全传输,但是依然没办法证明服务器是可靠的,例如黑客可以通过 DNS 劫持将服务器的 IP 地址替换成黑客的 IP 地址,这样访问的其实是黑客的服务器,黑客可以在自己的服务器上实现公钥和私钥,而对浏览器来说,它完全不知道现在访问的是黑客的站点。因此我们引入数字证书来让服务器向浏览器证明自己的身份。
5.4 数字证书
相较于第三版的 HTTPS 协议,这里主要有两点改变:
- 服务器没有直接返回公钥给浏览器,而是返回了数字证书,而公钥正是包含在数字证书中的;
- 在浏览器端多了一个证书验证的操作,验证了证书之后,才继续后续流程。
数字证书申请流程:
浏览器验证证书的流程:
浏览器需要验证证书的有效期(证书中就有)、证书是否被 CA 吊销(下载吊销证书列表或在线验证)、证书是否是合法的 CA 机构颁发的(如下图)。
浏览器是怎么获取到 CA 公钥的?
通常,部署 HTTP 服务器的时候,除了部署自己的数字证书之外,还需要部署 CA 机构的数字证书,CA 机构的数字证书包括了 CA 的公钥,以及 CA 机构的一些基础信息。然后在建立 HTTPS 链接时,服务器会将这两个证书一同发送给浏览器,于是浏览器就可以获取到 CA 的公钥了。
如果有些服务器没有部署 CA 的数字证书,那么浏览器还可以通过网络去下载 CA 证书,不过这种方式多了一次证书下载操作,会拖慢首次打开页面的请求速度,一般不推荐使用。
不过这样新的问题又来了,如果这个证书是一个恶意的 CA 机构颁发的怎么办?所以我们还需要浏览器证明这个 CA 机构是个合法的机构。
证明 CA 机构的合法性:
并没有一个非常好的方法来证明 CA 的合法性,妥协的方案是,直接在操作系统中内置这些 CA 机构的数字证书。但是CA 机构众多,不可能将每家 CA 的数字证书都内置进操作系统。
于是人们又将颁发证书的机构划分为两种类型,根 CA和中间 CA,通常申请者都是向中间 CA 去申请证书的,而根 CA 作用就是给中间 CA 做认证,一个根 CA 会认证很多中间的 CA,而这些中间 CA 又可以去认证其他的中间 CA,这样就形成了一个证书链,可以沿着证书链从用户证书追溯到根证书。浏览器先利用中间 CA 的数字证书来验证用户证书,再利用根证书来验证中间 CA 证书的合法性,最后,浏览器会默认相信内置在系统中的根证书。
不过如果黑客入侵了你的电脑,那么黑客就有可能往你系统中添加恶意根数字证书,那么当你访问黑客站点的时候,浏览器甚至有可能会提示该站点是安全的。因此,HTTPS 并非是绝对安全的,采用 HTTPS 只是加固了城墙的厚度,但是城墙依然有可能被突破。