一些相当基础的知识-前端安全部分

150 阅读26分钟

同源策略

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。

所谓同源是指:域名、协议、端口相同

下表是相对于 http://www.laixiangran.cn/home/index.html 的同源检测结果:

img

另外,同源策略又分为以下两种:

  1. DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。

    (iframe是一个html标签, 用于在网页内显示网页。语法:)

  2. XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

限制

你之所以会遇到 跨域问题,正是因为 SOP 的各种限制。但是具体来说限制了什么呢?

随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。

(1) Cookie、LocalStorage 和 IndexDB 无法读取。

(2) DOM 无法获得。

(3) AJAX 请求不能发送。

如果你说 SOP 就是“限制非同源资源的获取”,这不对,最简单的例子是引用图片、css、js 文件等资源的时候就允许跨域。

如果你说 SOP 就是“禁止跨域请求”,这也不对,本质上 SOP 并不是禁止跨域请求,而是在请求后拦截了请求的回应。这就就会引起后面说到的 CSRF

其实 SOP 不是单一的定义,而是在不同情况下有不同的解释:

  • 限制 cookies、DOM 和 JavaScript 的命名区域
  • 限制 iframe、图片等各种资源的内容操作
  • 限制 ajax 请求,准确来说是限制操作 ajax 响应结果,本质上跟上一条是一样的

下面是 3 个在实际应用中会遇到的例子:

  • 使用 ajax 请求其他跨域 API,最常见的情况,前端新手噩梦
  • iframe 与父页面交流,出现率比较低,而且解决方法也好懂
  • 对跨域图片(例如来源于 )进行操作,在 canvas 操作图片的时候会遇到这个问题

如果没有了 SOP:

  • 一个浏览器打开几个 tab,数据就泄露了
  • 你用 iframe 打开一个银行网站,你可以肆意读取网站的内容,就能获取用户输入的内容
  • 更加肆意地进行 CSRF

绕过跨域

SOP 带来安全,同时也会带来一定程度的麻烦,因为有时候就是有跨域的需求。绕过跨域的方案由于篇幅所限,并且网上也很多相关文章,所以不在这里展开解决跨域的方案,只给出几个关键词:

对于 ajax

  • 使用 jsONP
  • 后端进行 CORS 配置
  • 后端反向代理

对于 iframe

  • 使用 location.hash 或 window.name 进行信息交流
  • 使用 postMessage

为什么同源策略不能防CSRF

上面说了,SOP 可以通过 html tag 加载资源,而且 SOP 不阻止接口请求而是拦截请求结果,CSRF 恰恰占了这两个便宜。

所以 SOP 不能作为防范 CSRF 的方法

对于 GET 请求,直接放到就能神不知鬼不觉地请求跨域接口。

对于 POST 请求,很多例子都使用 form 提交:

<form action="<nowiki>http://bank.com/transfer.do</nowiki>" method="POST">
  <input type="hidden" name="acct" value="MARIA" />
  <input type="hidden" name="amount" value="100000" />
  <input type="submit" value="View my pictures" />
</form>

归根到底,这两个方法不报跨域是因为请求由 html 控制,你无法用 js 直接操作获得的结果。

SOP 与 ajax

对于 ajax 请求,在获得数据之后你能肆意进行 js 操作。这时候虽然同源策略会阻止响应,但依然会发出请求。因为执行响应拦截的是浏览器而不是后端程序。事实上你的请求已经发到服务器并返回了结果,但是迫于安全策略,浏览器不允许你继续进行 js 操作,所以报出你熟悉的 blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.。

所以再强调一次,同源策略不能作为防范 CSRF 的方法

不过可以防范 CSRF 的例外还是有的,浏览器并不是让所有请求都发送成功,上述情况仅限于简单请求,相关知识会在下面 CORS 一节详细解释。

为什么要有跨域限制

因为存在浏览器同源策略,所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到,跨域限制主要的目的就是为了用户的上网安全。

如果浏览器没有同源策略,会存在什么样的安全问题呢。下面从 DOM 同源策略和 XMLHttpRequest 同源策略来举例说明:

如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:

  1. 做一个假网站,里面用 iframe 嵌套一个银行网站 http://mybank.com
  2. 把 iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
  3. 这时如果用户输入账号密码,我们的主网站可以跨域访问到 http://mybank.com 的 dom 节点,就可以拿到用户的账户密码了。

如果 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:

  1. 用户登录了自己的银行页面 http://mybank.comhttp://mybank.com 向用户的 cookie 中添加用户标识。
  2. 用户浏览了恶意页面 http://evil.com,执行了页面中的恶意 AJAX 请求代码。
  3. http://evil.comhttp://mybank.com 发起 AJAX HTTP 请求,请求会默认把 http://mybank.com 对应 cookie 也同时发送过去。
  4. 银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。
  5. 而且由于 Ajax 在后台执行,用户无法感知这一过程。

因此,有了浏览器同源策略,我们才能更安全的上网。

跨域的解决方法

从上面我们了解到了浏览器同源策略的作用,也正是有了跨域限制,才使我们能安全的上网。但是在实际中,有时候我们需要突破这样的限制,因此下面将介绍几种跨域的解决方法。

CORS(跨域资源共享)

CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。

整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求

  1. 请求方法是以下三种方法之一:
  • HEAD
  • GET
  • POST
  1. HTTP的头信息不超出以下几种字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求

浏览器对这两种请求的处理,是不一样的。

简单请求

  1. 在请求中需要附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。例如:Origin: http://www.laixiangran.cn
  2. 如果服务器认为这个请求可以接受,就在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源,可以回发 * )。例如:Access-Control-Allow-Origin:http://www.laixiangran.cn
  3. 没有这个头部或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常情况下,浏览器会处理请求。注意,请求和响应都不包含 cookie 信息。
  4. 如果需要包含 cookie 信息,ajax 请求需要设置 xhr 的属性 withCredentials 为 true,服务器需要设置响应头部 Access-Control-Allow-Credentials: true

非简单请求

浏览器在发送真正的请求之前,会先发送一个 Preflight 请求给服务器,这种请求使用 OPTIONS 方法,发送下列头部:

  • Origin:与简单的请求相同。
  • Access-Control-Request-Method: 请求自身使用的方法。
  • Access-Control-Request-Headers: (可选)自定义的头部信息,多个头部以逗号分隔。

例如:

Origin: http://www.laixiangran.cn
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进行沟通:

  • Access-Control-Allow-Origin:与简单的请求相同。
  • Access-Control-Allow-Methods: 允许的方法,多个方法以逗号分隔。
  • Access-Control-Allow-Headers: 允许的头部,多个方法以逗号分隔。
  • Access-Control-Max-Age: 应该将这个 Preflight 请求缓存多长时间(以秒表示)。

例如:

Access-Control-Allow-Origin: http://www.laixiangran.cn
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

一旦服务器通过 Preflight 请求允许该请求之后,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样了。

优点

  • CORS 通信与同源的 AJAX 通信没有差别,代码完全一样,容易维护。
  • 支持所有类型的 HTTP 请求。

缺点

  • 存在兼容性问题,特别是 IE10 以下的浏览器。
  • 第一次发送非简单请求时会多一次请求。

JSONP 跨域

由于 script 标签不受浏览器同源策略的影响,允许跨域引用资源。因此可以通过动态创建 script 标签,然后利用script标签的src 属性进行跨域,这也就是 JSONP 跨域的基本原理。

详细描述:

  • 别人发现,Web页面上调用js文件时则不受是否跨域的影响(不仅如此,别人还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如script、img、iframe)
  • 于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、属于未来的HTML5之Websocket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;
  • 恰巧我们已经知道有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被js原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据;
  • 这样子解决方案就呼之欲出了,web客户端通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件(一般以JSON为后缀),显而易见,服务器之所以要动态生成JSON文件,目的就在于把客户端需要的数据装入进去。
  • 客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但其实并不一样。
  • 千言万语汇成一句话:JSONP是一段参数是json格式(大多数情况)的JS代码

直接通过下面的例子来说明 JSONP 实现跨域的流程:

// 1. 定义一个 回调函数 handleResponse 用来接收返回的数据
function handleResponse(data) {
    console.log(data);
};
​
// 2. 动态创建一个 script 标签,并且告诉后端回调函数名叫 handleResponse
var body = document.getElementsByTagName('body')[0];
var script = document.creatElement('script');
script.src = 'http://www.laixiangran.cn/json?callback=handleResponse';
body.appendChild(script);
​
// 3. 通过 script.src 请求 `http://www.laixiangran.cn/json?callback=handleResponse`,
// 4. 后端能够识别这样的 URL 格式并处理该请求,然后返回 handleResponse({"name": "laixiangran"}) 给浏览器
// 5. 浏览器在接收到 handleResponse({"name": "laixiangran"}) 之后立即执行 ,也就是执行 handleResponse 方法,获得后端返回的数据,这样就完成一次跨域请求了。

优点

  • 使用简便,没有兼容性问题,目前最流行的一种跨域方法。

缺点

  • 只支持 GET 请求。
  • 由于是从其它域中加载代码执行,因此如果其他域不安全,很可能会在响应中夹带一些恶意代码。
  • 要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给 script 标签新增了一个 onerror 事件处理程序,但是存在兼容性问题。

Nginx反向代理

图像 Ping 跨域

由于 img 标签不受浏览器同源策略的影响,允许跨域引用资源。因此可以通过 img 标签的 src 属性进行跨域,这也就是图像 Ping 跨域的基本原理。

直接通过下面的例子来说明图像 Ping 实现跨域的流程:

var img = new Image();
​
// 通过 onload 及 onerror 事件可以知道响应是什么时候接收到的,但是不能获取响应文本
img.onload = img.onerror = function() {
    console.log("Done!");
}
​
// 请求数据通过查询字符串形式发送
img.src = 'http://www.laixiangran.cn/test?name=laixiangran';

优点

  • 用于实现跟踪用户点击页面或动态广告曝光次数有较大的优势。

缺点

  • 只支持 GET 请求。
  • 只能浏览器与服务器的单向通信,因为浏览器不能访问服务器的响应文本。

服务器代理

浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。

服务器代理是万能的。

document.domain 跨域

对于主域名相同,而子域名不同的情况,可以使用 document.domain 来跨域。这种方式非常适用于 iframe 跨域的情况。

比如,有一个页面,它的地址是 http://www.laixiangran.cn/a.html,在这个页面里面有一个 iframe,它的 src 是 http://laixiangran.cn/b.html。很显然,这个页面与它里面的 iframe 框架是不同域的,所以我们是无法通过在页面中书写 js 代码来获取 iframe 中的东西的。

这个时候,document.domain 就可以派上用场了,我们只要把 http://www.laixiangran.cn/a.htmlhttp://laixiangran.cn/b.html 这两个页面的 document.domain 都设成相同的域名就可以了。但要注意的是,document.domain 的设置是有限制的,我们只能把 document.domain 设置成自身或更高一级的父域,且主域必须相同。例如:a.b.laixiangran.cn 中某个文档的 document.domain 可以设成 a.b.laixiangran.cnb.laixiangran.cnlaixiangran.cn 中的任意一个,但是不可以设成 c.a.b.laixiangran.cn ,因为这是当前域的子域,也不可以设成 baidu.com,因为主域已经不相同了。

例如,在页面 http://www.laixiangran.cn/a.html 中设置document.domain:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()">
<script>
    document.domain = 'laixiangran.cn'; // 设置成主域
    function test() {
        console.log(document.getElementById('myIframe').contentWindow);
    }
</script>

在页面 http://laixiangran.cn/b.html 中也设置 document.domain,而且这也是必须的,虽然这个文档的 domain 就是 laixiangran.cn,但是还是必须显式地设置 document.domain 的值:

<script>
    document.domain = 'laixiangran.cn'; // document.domain 设置成与主页面相同
</script>

这样,http://www.laixiangran.cn/a.html 就可以通过 js 访问到 http://laixiangran.cn/b.html 中的各种属性和对象了。

window.name 跨域

window 对象有个 name 属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面(不管是相同域的页面还是不同域的页面)都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

通过下面的例子介绍如何通过 window.name 来跨域获取数据的。

页面 http://www.laixiangran.cn/a.html 的代码:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 2. iframe载入 "http://laixiangran.cn/b.html 页面后会执行该函数
    function test() {
        var iframe = document.getElementById('myIframe');
        
        // 重置 iframe 的 onload 事件程序,
        // 此时经过后面代码重置 src 之后,
        // http://www.laixiangran.cn/a.html 页面与该 iframe 在同一个源了,可以相互访问了
        iframe.onload = function() {
            var data = iframe.contentWindow.name; // 4. 获取 iframe 里的 window.name
            console.log(data); // hello world!
        };
        
        // 3. 重置一个与 http://www.laixiangran.cn/a.html 页面同源的页面
        iframe.src = 'http://www.laixiangran.cn/c.html';
    }
</script>

页面 http://laixiangran.cn/b.html 的代码:

<script type="text/javascript">
    // 1. 给当前的 window.name 设置一个 http://www.laixiangran.cn/a.html 页面想要得到的数据值 
    window.name = "hello world!";
</script>

location.hash 跨域

location.hash 方式跨域,是子框架具有修改父框架 src 的 hash 值,通过这个属性进行传递数据,且更改 hash 值,页面不会刷新。但是传递的数据的字节数是有限的。

页面 http://www.laixiangran.cn/a.html 的代码:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 2. iframe载入 "http://laixiangran.cn/b.html 页面后会执行该函数
    function test() {
        // 3. 获取通过 http://laixiangran.cn/b.html 页面设置 hash 值
        var data = window.location.hash;
        console.log(data);
    }
</script>

页面 http://laixiangran.cn/b.html 的代码:

<script type="text/javascript">
    // 1. 设置父页面的 hash 值
    parent.location.hash = "world";
</script>

postMessage 跨域

window.postMessage(message,targetOrigin) 方法是 HTML5 新引进的特性,可以使用它来向其它的 window 对象发送消息,无论这个 window 对象是属于同源或不同源。这个应该就是以后解决 dom 跨域通用方法了。

调用 postMessage 方法的 window 对象是指要接收消息的那一个 window 对象,该方法的第一个参数 message 为要发送的消息,类型只能为字符串;第二个参数 targetOrigin 用来限定接收消息的那个 window 对象所在的域,如果不想限定域,可以使用通配符 *。

需要接收消息的 window 对象,可是通过监听自身的 message 事件来获取传过来的消息,消息内容储存在该事件对象的 data 属性中。

页面 http://www.laixiangran.cn/a.html 的代码:

<iframe src="http://laixiangran.cn/b.html" id="myIframe" onload="test()" style="display: none;">
<script>
    // 1. iframe载入 "http://laixiangran.cn/b.html 页面后会执行该函数
    function test() {
        // 2. 获取 http://laixiangran.cn/b.html 页面的 window 对象,
        // 然后通过 postMessage 向 http://laixiangran.cn/b.html 页面发送消息
        var iframe = document.getElementById('myIframe');
        var win = iframe.contentWindow;
        win.postMessage('我是来自 http://www.laixiangran.cn/a.html 页面的消息', '*');
    }
</script>

页面 http://laixiangran.cn/b.html 的代码:

<script type="text/javascript">
    // 注册 message 事件用来接收消息
    window.onmessage = function(e) {
        e = e || event; // 获取事件对象
        console.log(e.data); // 通过 data 属性得到发送来的消息
    }
</script>

CSRF

问的不难,一般问:

  • CSRF的基本概念、缩写、全称
  • 攻击原理
  • 防御措施

如果把攻击原理防御措施掌握好,基本没什么问题。

CSRF的基本概念、缩写、全称

CSRF(Cross-site request forgery):跨站请求伪造

PS:中文名一定要记住。英文全称,如果记不住也拉倒。

CSRF的攻击原理

img

用户是网站A的注册用户,且登录进去,于是网站A就给用户下发cookie。

从上图可以看出,要完成一次CSRF攻击,受害者必须满足两个必要的条件:

(1)登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接口时,会提示你登录)

(2)在不登出A的情况下,同一浏览器的不同tab中访问危险网站B(其实是利用了网站A的漏洞)。

(3)网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

(4)浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户的Cookie信息以及权限处理该请求,导致来自网站B的恶意代码被执行。

温馨提示一下,cookie保证了用户可以处于登录状态,但网站B其实拿不到 cookie。

举例:

受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求 bank.example/withdraw?ac… 可以使 Bob 把 1000000 的存款转到 bob2 的账号下。通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session 的用户 Bob 已经成功登陆。

黑客 Mallory 自己在该银行也有账户,他知道上文中的 URL 可以把钱进行转帐操作。Mallory 可以自己发送一个请求给银行:bank.example/withdraw?ac…。但是这个请求来自 Mallory 而非 Bob,他不能通过安全认证,因此该请求不会起作用。

这时,Mallory 想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码: src=”bank.example/withdraw?ac… ”,并且通过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一起发向银行服务器。大多数情况下,该请求会失败,因为他要求 Bob 的认证信息。但是,如果 Bob 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的 cookie 之中含有 Bob 的认证信息。这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 Bob 的账号转移到 Mallory 的账号,而 Bob 当时毫不知情。等以后 Bob 发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而 Mallory 则可以拿到钱后逍遥法外。

CSRF漏洞检测

检测CSRF漏洞是一项比较繁琐的工作,最简单的方法就是抓取一个正常请求的数据包,去掉Referer字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。

CSRF如何防御

方法一、Token 验证: (用的最多)

(1)服务器发送给客户端一个token;

(2)客户端提交的表单中带着这个token。

(3)如果这个 token 不合法,那么服务器拒绝这个请求。

CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

(一般form表单加个hidden属性的input标签来存放token)csrf就没法获取token,这样我们就可以通过检测发送过来的数据包中是否有正确的token值来决定是否响应请求。

在讲清token防御的原理后,我们再来讲token的设计,因为token方式给人的感觉很复杂令人望而生畏。

我们首先明确一个问题,就是能够防止csrf攻击的token,并不需要每次请求都不一样,在用户登录后到退出前的这整个过程中的所有请求token完全可以是一样。因为(在基于没有其他漏洞会泄漏本次会话的token的设想下)黑客是无法获取用户的tokne,所以又何必每个请求都要生成一个新的token呢。(token每次请求都要不一样的想法是受防重放攻击的影响)只考滤防csrf不考滤防重放的情况下,token设计就简单多了。

使用sessionid作为token设计:在csrf中cookie是浏览器自己带上的,本质而言用户的sessionid并未丢失(也就是攻击者并不能知道sessionid是多少),基于此我们完全可以不用另传一个值只需直接将sessionid作为token即可(或者也可以做些运算比如取sessionid的某些值做个md5来做为token,意思都差不多)。判断代码类似 if session["id"] == $_POST["token"]

与sessionid同时返回的token设计:在生成sessionid的同时生成一个token(服务端token可以存于session变量中)返回给客户端,客户端保存该token每次请求时都在form表单中提交该值。判断代码类似if session["token"] == $_POST["token"]

方法二:隐藏令牌:

把 token 隐藏在 http 的 head头中。

方法二和方法一有点像,本质上没有太大区别,只是使用方式上有区别。

方法三、Referer 验证:

Referer 指的是页面请求来源。意思是,只接受本站的请求,服务器才做响应;如果不是,就拦截。

Referer标识当前请求的来源页面,浏览器访问时除了自动带上Cookie还会自动带上Referer,所以服务端可以检测Referer头是否本网站页面来决定是否响应请求。

Referer是浏览器自动带上的,基于认为浏览器没有相关漏洞的前提下,我们可以认为攻击者是没法伪造Referer头的,也就是检测Referer头的方法是可靠的。

但该方式有时会不受认可,一是因为浏览器是可以设置禁止发送Referer头的,如果使用该方式那么禁止Referer头的浏览将无法正常使用,这可能会降低用户使用体验。二是因为由于移动端的崛起当下流行前后端分离app和web共用一套后端代码,但app是不会自动带Referer头的,如果使用该方式app端不好处理。

根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问 bank.example/withdraw?ac…,用户必须先登陆 bank.example,然后通过点击页面上的按钮来触发转账事件。这时,该转帐请求的 Referer 值就会是转账按钮所在的页面的 URL,通常是以 bank.example 域名开头的地址。而如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客自己的网站。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其 Referer 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。

这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。

然而,这种方法并非万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如 IE6 或 FF2,目前已经有一些方法可以篡改 Referer 值。如果 bank.example 网站支持 IE6 浏览器,黑客完全可以把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就可以通过验证,从而进行 CSRF 攻击。

即便是使用最新的浏览器,黑客无法篡改 Referer 值,这种方法仍然有问题。因为 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心 Referer 值会把组织内网中的某些信息泄露到外网中。因此,用户自己可以设置浏览器使其在发送请求时不再提供 Referer。当他们正常访问银行网站时,网站会因为请求没有 Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问。

XSS

1、XSS的基本概念

XSS(Cross Site Scripting):跨域脚本攻击

接下来,我们详细讲一下 XSS 的内容。

预备知识:HTTP、Cookie、Ajax。

XSS的攻击原理

XSS攻击的核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。

最后导致的结果可能是:

  • 盗用Cookie
  • 破坏页面的正常结构,插入广告等恶意内容
  • D-doss攻击

XSS的攻击方式

  • 1、反射型

发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。

  • 2、存储型

存储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。

XSS的防范措施(encode + 过滤)

XSS的防范措施主要有三个:

1、编码

对用户输入的数据进行HTML Entity编码。

如上图所示,把字符转换成 转义字符。

Encode的作用是将$var等一些字符进行转化,使得浏览器在最终输出结果上是一样的。

比如说这段代码:

<script>alert(1)</script>

1

若不进行任何处理,则浏览器会执行alert的js操作,实现XSS注入。

进行编码处理之后,L在浏览器中的显示结果就是<script>alert(1)</script>,实现了将$var作为纯文本进行输出,且不引起JavaScript的执行。

参考链接:4类防御XSS的有效方法

2、过滤:

  • 移除用户输入的和事件相关的属性。如onerror可以自动触发攻击,还有onclick等。(总而言之,过滤掉一些不安全的内容)
  • 移除用户输入的Style节点、Script节点、Iframe节点。(尤其是Script节点,它可是支持跨域的呀,一定要移除)。

3、校正

  • 避免直接对HTML Entity进行解码。
  • 使用DOM Parse转换,校正不配对的DOM标签。

备注:我们应该去了解一下DOM Parse这个概念,它的作用是把文本解析成DOM结构。

比较常用的做法是,通过第一步的编码转成文本,然后第三步转成DOM对象,然后经过第二步的过滤。

还有一种简洁的答案:

首先是encode,如果是富文本,就白名单。

CSRF 和 XSS 的区别

面试官还可能喜欢问二者的区别。

区别一:

  • CSRF:需要用户先登录网站A,获取 cookie。
  • XSS:不需要登录。

区别二:(原理的区别)

  • CSRF:是利用网站A本身的漏洞,去请求网站A的api。
  • XSS:是向网站 A 注入 JS代码,然后执行 JS 里的代码,篡改网站A的内容。
攻击攻击链接示例说明
CSRFwww.hack.com/csrf_page(页面中含src="www.bank.com/transferFun…“)发送的是hack网站的页面,目标是bank网站页面
XSSwww.bank.com/xss_page?xs…'>'+document.cookie'发送的是bank网站的页面,目标是hack网站页面