XSS 攻击与防范
跨站脚本攻击
跨站脚本攻击,Cross-site scripting, 缩写 XSS, ( 以前的文档也有写作 CSS 的,但跟层叠样式表的缩写一样,为了区分,现在一般称作 XSS ),是一种非常常见的网络攻击方式,类似于 SQL 注入,也是一种注入攻击。
XSS 通过在网页中插入危险脚本,可以绕过同源策略之类的安全机制,获得执行危险操作的权限,达到攻击的目的。这些攻击可能涵盖访问敏感信息、篡改 HTML 内容、跳转到恶意网站、监控用户输入等等等,具有相当大的威胁。
攻击的逻辑
- 对于服务器来说,攻击代码是是一种普通数据(如一段 HTML 字符串),对服务器本身是没有特殊意义的,不会对服务器的行为有影响
- 对于客户端来说,攻击代码(如 HTML )是一种有特定意义的指令,会影响客户端的行为
- XSS 攻击通常会在客户端传递经过精心设计的恶意内容给服务器,这些数据会影响服务器正常组装 HTML、JavaScript、CSS 等指令代码的过程,让最终组装出来的指令代码包含恶意内容。
比如,通过传递经过巧妙设计的 URL 查询参数,让服务器使用该参数组装 HTML 标签时,意外提前闭合 HTML 标签,然后接续上一段攻击代码,这些包含攻击代码的 HTML 在浏览器上就反映为攻击。
XSS 的类型
XSS 根据特点,可以分为几种类型。
反射型 XSS ( Reflected XSS )
反射型 XSS,即非持久型( non-persistent ) XSS,主要是由于服务器端收到客户端的不安全输入(如一个未验证的 URL 查询参数),在客户端触发从而发起攻击。
过程:
- 客户端提交有害输入
- 服务器接收有害输入
- 服务器组装出包含攻击的代码
- 客户端执行包含攻击的代码
反射型 XSS,仅在当次访问中生效,一般是在哪个页面攻击,就在该页面生效,注入的恶意代码,不会影响到其他网页。
例如 URL 参数中包含了攻击代码,若服务器不加过滤地使用了这些攻击代码,组装成的 HTML并发送给浏览器解析执行,一旦用户访问,就会遭受攻击。
假如访问:http://xssexample.com/?name=world
时,输出
1
Hello world
其中 world
从 URL 查询参数name=world
中得到,如果服务器不严格检测name
参数的值,那么就存在 XSS 漏洞,例如,攻击者诱导用户访问:
http://xssexample.com/?name=<script>alert('xss')</script>
时,就会弹出一个内容为 xss
的警告框,因为这时候输出的 HTML 是这样的:
1
Hello <script>alert('xss')</script>
这只是个恶作剧性质的攻击代码,如果攻击者注入的是带着不可告人目的的恶意代码,那么后果不堪设想。
存储型 XSS ( Stored XSS )
通常漏洞发生在用户提交的文章、留言评论、资料填写等需要存储到服务器的场景,服务器如果没有做严格的输入检查,就将内容不做处理地持久化存储,就可能存储了危险代码,用户访问这些内容的时候就形成了攻击。
存储型 XSS,本质上根反射型 XSS 并无区别。只是需要事先向服务器提交恶意数据,将数据做持久化存储,用户访问受影响的页面时,服务器从持久层拿数据,并组装成带攻击的 HTML 页面。因此相比反射型 XSS,仅仅多了一个持久化的环节。
过程:
- 客户端提交有害输入
- 服务器存储有害输入
- 服务器取出有害输入
- 服务器组装出包含攻击的代码
- 客户端执行包含攻击的代码
效果上,存储型 XSS,攻击页面跟受攻击影响的页面之间没有绑定关系,因为同样一个数据,可能在页面多个地方、在多个页面中使用。例如,一个用户名字,可能在会员中心、列表、详情等等页面都会用上,所以,影响更广泛。
基于 DOM 的 XSS ( DOM Based XSS )
这是一种特殊的反射式 XSS。只是不是反射自服务器,而是反射自客户端 JavaScript,使用客户端 JavaScript 代替服务器的作用,全程在客户端实现攻击。
过程:
- 客户端提交有害输入
- 客户端 JavaScript 处理有害输入
- 客户端 JavaScript 组装出包含攻击的代码
- 客户端执行包含攻击的代码
例如一个页面中,有一个 AJAX 获取外部数据并输出到页面的功能,如果 AJAX 获取的数据源是不可信的( 例如是来自第三方数据接口 ),并且输出数据的时候,也没有做严格的检查和处理( 例如直接作为 innerHTML
插入 ),则存在 XSS 风险。攻击者只需要在数据源中添加有害的数据,那么,受影响的页面就会输出攻击代码。
这种攻击方式更难以察觉和定位问题。
XSS 攻击输出点
XSS 攻击代码,可能出现在 HTML 文件中的许多地方。
HTML 标签之间
1
2
3
<div> XSS </div>
<script> XSS </script>
<style> XSS </style>
HTML 注释里
1
<!-- XSS -->
HTML 标签名
1
<XSS>...</XSS>
HTML 标签属性名
1
<div XSS=""></div>
HTML 标签属性值
1
<div name="XSS"></div>
防御攻击
核心思想是,不信任用户的输入,对所有安全性无法保证的数据,做好输入输出的检查、过滤。在需要插入安全性无法确保的数据的地方,要将数据做相应地编码处理。
对于不同类型的插入内容,需要做的编码规则也各不相同。
HTML 标签之间的内容的编码
对于 HTML 标签之间的插入内容,需要对几个敏感的字符做 HTML Entity 编码。
安全原则:
&
编码为&
<
编码为<
>
编码为>
"
编码为"
\
编码为\
/
编码为/
HTML 标签属性值的编码
插入内容到 HTML 属性值里的时候,需要对内容做 HTML 属性编码。
安全原则:
- 阿拉伯数字和字母无需编码
- ASCII 码小于 256 的字符,按照
&#xHH;
的形式编码,HH
对应该自负的十六进制数字 - 属性值部分应该正确使用引号包裹,否则较容易被提前闭合造成 XSS 漏洞,若没有引号,又没有进行上面第2点提到的编码,那么攻击者只需要插入一个空格即可提前闭合该属性,并紧接着插入攻击脚本。然后,
%
*
+
,
–
/
;
<
=
>
^
|
`(IE将反单引号当做单引号) 等符号也可用来闭合属性。如下例
1
<div width=$var></div>
那么,攻击者只需要输入:10 onmouseover="javascript:alert('xss')"
,即可闭合掉该属性,并额外插入一段攻击代码。最终输出到浏览器的 HTML 是:
1
<div width=10 onmouseover="javascript:alert('xss')"></div>
script 内容的编码
对于插入的 JavaScript 脚本,包括在 HTML 标签的事件处理属性的属性值部分( 如 onclick ),要做 script 编码。
安全原则:
- 阿拉伯数字、字母无需编码
- ASCII 码小于 256 的字符,以
\xHH
的形式编码,HH
对应该字符的十六进制数字 - 只允许这些编码后的内容,插入在用引号括起来的值的部分,如:
1
2
3
<script>
var insert = "<%= encodeJavaScript(@INPUT) %>";
</script>
- 注意像
setInterval
之类的函数,就算进行了 JavaScript 编码,依然存在 XSS 漏洞 - 仅用反斜杠
\
转义是不可靠的,因为浏览器会先进行 HTML 解析,再做 JavaScript 解析,像用反斜杠转义的双引号,可能会被当作 HTML 字符解析,从而出现 XSS 漏洞,如下例
1
2
3
<script>
var a = "$var";
</script>
攻击者输入 \"; alert('xss');//
,即可将最终 HTML 输出变成:
1
2
3
<script>
var a = "\\"; alert('xss');//";
</script>
浏览器在解析的时候,会认为反斜杠后面的那个双引号和第一个双引号相匹配,继而认为后续的 alert('xss')
是正常的 JavaScript 脚本。
样式表内容编码
当需要往 style
标签或者 style
属性里插入不可信数据的时候,需要对这些数据进行 CSS 编码。
安全原则:
- 阿拉伯数字和字母不用编码
- ASCII 码小于 256 的字符以
\HH
的形式编码,HH
为该字符对应的十六进制数字 - 只插入到 CSS 属性的值部分
- 最好不要放到
url
,behavior
等等复杂属性里,还有 IE 的 CSS 表达式中也要尽量避免
URL 内容的编码
当需要插入安全性无法保证的 URL 时,需要进行 URL 编码。
安全原则:
- 阿拉伯数字和字母不用编码
- ASCII 码小于 256 的字符以
%HH
的形式编码,HH
为该字符对应的十六进制数字 - URL 属性应该用引号包围内容,避免被轻易闭合
- 不对整个URL进行编码,而要根据插入的位置( 如
href
、src
等等 ),进行恰当的起始部分的协议字段的验证。( 如data
协议、javascript
伪协议等等)