这看上去是一个标准的标签语言,但并不会触发xss,因为当前HTML解析器处于“数据状态”,不会转换到“标签开始状态”,所以就不会建立新的标签。因此,我们能够利用字符实体编码这个行为来转义用户输入的数据从而确保用户输入的数据只能被解析成“数据”而不是XSS攻击向量。
(2)属性值状态中的字符引用:属性值状态中的字符引用就好理解了,就是src,herf这样的属性值中的HTML实体,他也是会先进行HTML解码的,比如下面的语句,会先对里面HTML解码,然后再继续往下执行:
<a href=javascript:alert("xss")>test</a>
(3)RCDATA状态中的字符引用:然后再来看一下什么是RCDATA转态,这里需要我们先了解一下HTML中有五类元素:
- 空元素(Void elements),如 <area>、<br>、<base> 等等。空元素不能容纳任何内容,因为它们没有闭合标签,没有内容能够放在开始标签和闭合标签中间。
- 原始文本元素(Raw text elements),有 <script> 和 <style>。原始文本元素可以容纳文本。
- RCDATA元素(RCDATA elements),有 <textarea> 和 <title>。RCDATA元素可以容纳文本和字符引用。
- 外部元素(Foreign elements),例如MathML命名空间或者SVG命名空间的元素。外部元素可以容纳文本、字符引用、CDATA段、其他元素和注释。
- 基本元素(Normal elements),即除了以上4种元素以外的元素。基本元素可以容纳文本、字符引用、其他元素和注释。
注意到RCDATA元素中有 <textarea> 和 <title> 两个属性并且有字符引用,也就是当实体字符出现在这两个标签里面的时候,实体字符会被识别并进行HTML编码解析。这里要再提醒一次,在解析这些字符引用的过程中不会进入“标签开始状态”,所以就不会建立新的标签,所以下面这个语句触发不了XSS:
<textarea><script>alert("xss")</script></textarea>
但是如果直接放进去标签的内容呢,不带转义字符呢,如下:
<textarea><script>alert("xss")</script></textarea>
同样也是不会触发XSS的:
这涉及到了RCDATA的一个特殊的情况。即在浏览器解析RCDATA元素的过程中,解析器会进入“RCDATA状态”。在这个状态中,如果遇到“<”字符,它会转换到“RCDATA小于号状态”。如果“<”字符后没有紧跟着“/”和对应的标签名,解析器会转换回“RCDATA状态”,并不会进入“标签开始状态”的。这意味着在RCDATA元素标签的内容中,唯一能够被解析器认做是标签的就只有 </textarea> 或者 </title>,因此,在 <textarea> 和 <title> 的内容中不会创建标签,就不会有脚本能够执行了。
另外还有一点要注意:我们从上面HTML的五类元素中还发现有一个原始文本元素 <script> 在这个标签内容纳的是文本,所以浏览器在解析到这个标签后,里面内容中的HTML编码并不会被认为是HTML实体引用,所以并不会被解码为相应的字符。浏览器看不懂中间这堆编码是和啥东西,所以也不会被执行,如下:
<script>alert("xss")</script>
那么如何才能让里面的内容进行转义并执行弹窗呢,这里需要利用到XSS的一个黑魔法——“svg”,我们下文中会提及。
URL编码我们可以并将src或href属性中的内容进行URL编码,当HTML解析器对src或href中的字符完成HTML解码后,接下来URL解析器会对src或href中的值进行URL解码。
<a href="...">xx</a>
<iframe src="...">
下面给出几个实例。
- <a href=javascript:alert("xss")>test</a>
<a href=javascript:alert("xss")>test</a>
- <iframe src=javascript:alert("xss")></iframe>
<iframe src="javascript:alert("xss")"></iframe>
注意,伪协议头 javascript: 是不能进行编码的。这里就有一个URL解析过程中的一个细节了,即不能对协议类型进行任何的编码操作,否则URL解析器会认为它无类型,就会导致DOM节点中被编码的“javascript”没有被解码,当然不会被URL解析器识别了。就比如说 http://www.baidu.com 可以被URL编码为 http://www.baidu.com,但是不能把协议也进URL编码:http://www.baidu.com 。
但是伪协议头 javascript: 可以进行HTML编码。
Javascript 编码我们可以将DOM节点中的内容转化为 Javascript 编码。当HTML解析产生DOM节点后,会根据DOM节点来做接下来的解析工作,比如在处理诸如 <script>、<style> 这样的标签时,解析器会自动切换到JavaScript解析模式,而 src、 href 后边加入的 javascript 伪URL,也会进入 JavaScript 的解析模式。
Javascript 中可以识别的编码类型有:
- Unicode 编码
- 八进制编码
- 十六进制编码
一般情况下我们使用Unicode编码的比较广泛,而八进制和十六进制只有在DOM环境或eval()等函数中才可以用。
Unicode 编码- <script>alert("xss")</script>
<script>\u0061\u006C\u0065\u0072\u0074("xss")</script>
<script>\u0061\u006C\u0065\u0072\u0074("\u0078\u0073\u0073")</script>
- <a href=javascript:alert("xss")>test</a>
<a href=javascript:\u0061\u006C\u0065\u0072\u0074("xss")>test</a>
<a href=javascript:\u0061\u006C\u0065\u0072\u0074("\u0078\u0073\u0073")>test</a>
但要注意,我们同样也不能对伪协议头 javascript: 进行 Javascript 编码。并且像圆括号、双引号、单引号这样的符号我们也不能进 Javascript 编码,但是能进行HTML编码。
在DOM环境中的JavaScript编码对于八进制编码和十六进制编码,与 Unicode 编码还是有区别,像下面的XSS向量是不能直接执行的:
- <script>alert("xss")</script>
<script>\141\154\145\162\164("xss")</script>
- <a href=javascript:alert("xss")>test</a>
<a href=javascript:\x61\x6c\x65\x72\x74("xss")>test</a>
如下图,插入之后没有任何反应: