从浏览器的解析规则认识XSS防御

前言

作为 Web 漏洞家族常见的一员 —— XSS 跨站脚本攻击的身影依旧活跃在各类网站,而特殊字符转义作为 XSS 漏洞的主要修复和防御手段,被开发人员广泛应用。本文目的在于通过具体的代码实例,从浏览器的解析规则出发,分析 HTML 转义、JS 转义防御 XSS 攻击背后的原理,从而深入理解 XSS 漏洞。

浏览器解析

解析规则

浏览器在解析 HTML 文档时无论按照什么顺序,主要有三个过程:HTML 解析、JS 解析 和 URL 解析,每个解析器负责HTML文档中各自对应部分的解析工作。下面以一篇HTML文档解析来简单的讨论下解析器如何协同工作的。

  1. 首先浏览器接收到一个 HTML 文档时,会触发 HTML 解析器对 HTML 文档进行词法解析,这一过程完成 HTML解码 并创建 DOM 树;
  2. 接下来 JavaScript 解析器会介入对内联脚本进行解析,这一过程完成 JS 的解码工作;
  3. 如果浏览器遇到需要 URL 的上下文环境,这时 URL 解析器也会介入完成 URL 的解码工作。

URL 解析器的解码顺序会根据 URL 所在位置不同,导致在 JavaScript 解析器之前或之后解析。每个解析过程中也有许多细节,下面再做具体讨论。

字符编码

  1. HTML字符实体:
    在呈现 HTML 页面时,针对某些特殊字符如“<””>”直接使用,浏览器会误以为它们标签的开始或结束,若想正确的在 HTML 页面呈现特殊字符就需要用到其对应的字符实体。HTML 字符实体以& 开头 + 预先定义的实体名称,以分号结束,如“<”的实体名称为&lt; 或以 & 开头+#符号以及字符的十进制数字(或者 十六 进制,都能解析),如”<”的实体编号为&#60;
  2. JavaScript 编码:最常用的如 “\uXXXX” 这种写法为 Unicode 转义序列,表示一个字符,其中 xxxx 表示一个 16 进制数字,如”<” Unicode 编码为“\u003c”。
  3. URL编码%加字符的 ASCII 编码对于的 2 位 16 进制数字,如”/”对应的 URL 编码为%2f

HTML 转义字符表 可以参考:
在这里插入图片描述HTML在线编码转换 可以参考:
在这里插入图片描述

解析结果

简而言之,&#**;格式的字符串是 HTML 的转义字符,\是JS的转义符,转义的目的就是告诉解析器该符号为字符,而不是代码,防止代码出现歧义。

HTML 源代码 DOM 结构和浏览器解析后的 DOM 结构是不一样的:

  1. 在浏览器中我们右键查看网页源码(或者按住快捷键ctrl+u),看到的是服务器接受我们的请求后返回的 HTML 源码。
  2. 而按F12进入开发者工具面板,开发者工具分析出的 DOM 结构,就是浏览器的解析结果。

转义实例

JS 转义

来看一段测试代码 test1.php:

<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>一个XSS</title>
</head>
<body>
	<script type="text/javascript">
		var input_str = "<?php echo $_POST['str']?>";
		if(input_str.length>0){
			document.write("Your input:"+input_str);
		}
	</script>
	<form action="" method="post">
		<input type="text" name="str" />
		<input type="submit" value="提交">		
	</form>
</body>

将其部署在 phpstudy 的 WWW 文件夹下:
在这里插入图片描述
启动 phpstudy,访问测试页面:
在这里插入图片描述
当我们提交<script>alert(1)</script>,前端并没有出现我们期待的弹窗,而是输出了以下字符串:
在这里插入图片描述
而当我们提交<script>alert(1)<\/script>,却则可以正常弹框:
在这里插入图片描述
如何解释这两种情况,我们来思考一下?

原理分析

当我们输入第一个 payload :<script>alert(1)</script> 提交后,得到的 html 源码如下:
在这里插入图片描述
当我们的 HTM L解析器解析到 <script> 标签时,它会快速去查找离它最近的闭合标签</script>

  1. 这时它查找到是8行中的</script>,而不是12行的</script>
  2. 这时<script>标签内的var input_str = "<script>alert(1);被交给 js 引擎去解析。而8行</script>和12行的</script>之间的代码被当成字符串输出到前端页面。
  3. 而由于6行</script>标签没有配对成功,故不会被浏览器解析为一个合法标签。 所以最终的解析结果是第8行的<script>被解析为字符串,</script>被解析为html标签。

故最终浏览器解析后的 DOM 结构如下:
在这里插入图片描述
当我们输入第二个 payload:<script>alert(1)<\/script> 提交后,得到的 html 源码如下:
在这里插入图片描述
与上面代码类似,只是差异只在第8行(多了一个/)。

  1. 但还是同样的解析原则,html 解析引擎解析到7行的<script>时,它会快速去查找离它最近的闭合标签</script>。在到第8行时发现<\/script>标签,而不是</script>,故继续往下,直到找寻到12行的标签,才完成了配对。
  2. 这时8行和11行的代码交给了 js 引擎去解析。 而这时又因为\为 js 语法中的转义字符,故在 js 解析引擎解析时,又能正常解析input_str变量的值为<script>alert(1)</script>字符串,所以最总成功弹窗,很巧妙!

故最终浏览器解析后的 DOM 结构如下:
在这里插入图片描述从上面实例我们可以具体了解到服务端返回的源码的 DOM 结构与浏览器解析后的 DOM 结构的差异,以及 JS 转义的巧妙作用!

Html转义

来看看另一段测试代码 test2.php:

<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>XSS与html编码</title>
</head>
<body>
	<?php
		if(isset($_POST['submit'])){
			echo "<a href='".$_POST['str1']."'>str1</a>";
			echo "<br/>";
			echo "str2:".$_POST['str2'];
		}
	?>
	<form action="" method="post">
		str1:<input type="text" name="str1" />
		<br/>
		str2:<input type="text" name="str2" />
		<br/>
		<input type="submit" name="submit" value="提交">		
	</form>
</body>
</html>

同样放在 phpstudy 网站目录下,浏览器访问:
在这里插入图片描述
我们将javascript:alert(1);进行 html 转义(字符的十六进制)得到如下字符串,并填写到 str1 输入框:

&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3a;&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;&#x3b;

同时将<script>alert(1);</script>进行 html 转义后得到如下字符,并填写到str2输入框:

&lt;script&gt;alert(1)&lt;/script&gt;

提交后发现点击 str1 链接可以弹框,说明前者被当代码来执行了,而后者被当字符串输出了:
在这里插入图片描述

如何解释以上结果?

原理分析

提交 payload 之后,服务器返回的 html 源码代码如下:
在这里插入图片描述而浏览器 html 解析器解析后的结果如下:
在这里插入图片描述从上面可以看到,两个 payload 其实在浏览器的 HTML 解析器解析之后都被当成了字符输出了。

  1. 只是当用户再次点击 str1 的链接时,前者被解码之后的字符会被浏览器的 JS 解析器当成代码执行了。
  2. 而后者&lt;script&gt;alert(1);&lt;/script&gt;被浏览器 html 解析器解码后已经被当作一个普通字符串而非 js 代码(请重点注意<script>alert(1)</script>此时被浏览器解析后的结果是跟 str2:一起被包围在双引号里当作一个字符串显示),所以已无法被执行。

可以进一步来看看 str2 直接输入<script>alert(1)</script>的话是什么效果,答案是会触发弹框:
在这里插入图片描述
进一步看看浏览器的解析结果:
在这里插入图片描述
此时,<script>alert(1)</script>已经不是跟“str2:"一样被当作字符串输出了。

由上面的实例分析,可以清晰地看到 HTML 转义字符对于 XSS 防御的意义,它会告诉浏览器将一些危险字符当作普通字符串输出,而不是当作 js 代码执行。

自动转义

PHP 语言里,我们在服务端可以使用htmlspecialchars($str);函数对用户传输过来的客户端输入值进行 HTML 转义。

htmlspecialchars($str);函数只转换5个字符,即:和号(&),双引号(“),单引号(‘),小于(<),大于(>),将它们转换为 HTML 实体形式,输出时浏览器会自动还原并将其当成普通字符输出的。

利用htmlspecialchars($str);我们对前面 JS 转义的测试代码 test1.php 进行变更:

<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>一个XSS</title>
</head>
<body>
	<script type="text/javascript">
		var input_str = "<?php echo htmlspecialchars(($_POST['str']))?>";
		if(input_str.length>0){
			document.write("Your input:"+input_str);
		}
	</script>
	<form action="" method="post">
		<input type="text" name="str" />
		<input type="submit" value="提交">		
	</form>
</body>
</html>

此时再在浏览器输入 payload:<script>alert(1)<\/script>,已经无法触发弹框了,而是被当作普通字符输出:
在这里插入图片描述

看看网页源码就更加清晰了:
在这里插入图片描述
最后来看看某次测试时发现 XSS 漏洞,客户修复后的结果:

在这里插入图片描述
可以看到,<script>alert(1)<\/script>可以成功插入并正常显示,但是已无法触发弹框,而根本原因,点击数据查询按钮,查看服务器的响应数据就可以发现已经进行 HTML 转义了:

在这里插入图片描述

进阶示例

先来继续看一个实例代码 123.html:

<a href="javascript:alert(1)">test</a>
<a href="%6A%61%76%61%73%63%72%69%70%74:%61%6C%65%72%74%28%31%29">test1</a>
<a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;:%61%6C%65%72%74%28%32%29">test2</a>
<a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#51;&#49;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#54;&#51;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#51;&#53;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#55;&#37;&#51;&#50;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#55;&#37;&#51;&#52;&#40;&#51;&#41;">test3</a>

来看看实际页面:
在这里插入图片描述
其中各个链接的编码过程依次是:

链接编码
test1URL 编码 "javascript:alert(1)”
test2HTML字符实体编码 “javascript” 、URL 编码 “alert(2)”
test3对 "javascript:alert(1)” 先进行 JS编码、然后URL编码、最后HTML编码

实际运行效果是,test2、test3 可以触发弹窗,而 test1 则不能。
在这里插入图片描述
在这里插入图片描述分析下原因:

  1. test1 为何不能弹窗?
    URL 解析器解析过程中有一个细节了,不能对协议类型进行任何的编码操作,否则URL解析器会认为它无类型,这就导致 test1 中被 URL 编码的“javascript”没能成功解码,当然不会被URL解析器识别了。

  2. test2 为何能弹窗?
    "javascript"是做的HTML实体编码,HTML解析器工作时,href 里的 HTML 实体会被解码,接下来 URL 解析器工作解析 href 属性里的链接时,"javascript"协议在第一步被 HTML 解码了,这样 URL 解析器是可以识别的,然后继续解析后面的”%61%6C%65%72%74%28%32%29”,最后 JavaScript 解析器完成解析操作,脚本执行。

  3. test3为何能弹窗?
    test3 实现了 3 层复合编码,每一层编码都能正常执行。

以上我们直观地通过具体的实例代码了解了浏览器解析规则、字符转义与 XSS 漏洞之间的关系,下面还将从底层进一步分析它们的关系。

解析器原理

浏览器无论什么情况都会遵守一个这样的解码规则:

  1. HTML 解析器对 HTML 文档进行解析,完成 HTML 解码并且创建 DOM树;
  2. JavaScript 或者 CSS 解析器对内联脚本进行解析,完成 JS、CSS 解码;
  3. URL 解码会根据 URL 所在的顺序不同而在 JS 解码前或者解码后执行。

Html解析器

HTML中有五类元素:

  1. 空元素(Void elements),有area、base、br、col、command、embed、hr、img、input、keygen、link、meta、param、source、track、wbr;
  2. 原始文本元素(Raw text elements),有<script>和<style>
  3. RCDATA 元素(RCDATA elements),有<textarea>和<title>
  4. 外部元素(Foreign elements),例如 MathML 命名空间或者 SVG 命名空间的元素;
  5. 基本元素(Normal elements),即除了以上4种元素以外的元素。

五类元素的区别如下:

  1. 空元素,不能容纳任何内容(因为它们没有闭合标签,没有内容能够放在开始标签和闭合标签中间);
  2. 原始文本元素,可以容纳文本;
  3. RCDATA元素,可以容纳文本和字符引用;
  4. 外部元素,可以容纳文本、字符引用、CDATA段、其他元素和注释;
  5. 基本元素,可以容纳文本、字符引用、其他元素和注释。

HTML 解析器以状态机的方式运行,它从文档输入流中消耗字符并根据其转换规则转换到不同的状态。
在这里插入图片描述
示例:

<html>
 <body>
   Hello world
 </body>
</html>

以上 HTML 文档,浏览器 HTML 解析器的状态机运行过程为:

  1. 初始状态为"Data State",当遇到 “<” 字符,状态变为 “Tag open state”,读取一个 a-z 的字符将产生一个开始标签符号,状态相应变为 “Tag name state”,一直保持这个状态直到读取到 “>”,每个字符都附加到这个符号名上,例子中创建的是一个 html符号。
  2. 当读取到 “>”,当前的符号就完成了,此时状态回到"Datastate","<body>"重复这一处理过程。到这里,html 和 body 标签都识别出来了。现在,回到 “Data state”,读取 “Helloworld” 中的字符 “H” 将创建并识别出一个字符符号,这里会为 “Hello world” 中的每个字符生成一个字符符号。
  3. 这样直到遇到"</body>"中的 “<”。现在又回到了 “Tag open state”,读取下一个字符 “/” 将创建一个闭合标签符号,并且状态转移到 “Tag name state”,还是保持这一状态,直到遇到 “>”。然后产生一个新的标签符号并回到 “Data state”。后面的"</html>"将和"</body>"一样处理。

【注意】 HTML 解析器处于 数据状态(Data State)RCDATA 状态(RCDATA State)属性值状态(Attribute ValueState)时,字符实体会被解码为对应的字符。

示例:

<div>&#60;img src=x οnerrοr=alert(4)&#62;</div>
<>被编码为字符实体&#60;&#62;。
当HTML解析器解析完<div>时,会进入数据状态(Data State)并发布标签令牌。
接着解析到实体&#60;时因为处在数据状态(Data State)就会对实体进行解码为<
后面的&#62;同样道理被解码为>

这里会有个问题,被解码后,此处的 img 是否会被解析为 HTM L标签而导致 JS 执行呢?答案是否定的。

因为解析器在使用字符引用后不会转换到标签打开状态(Tag Open State),不进入标签打开状态就不会被发布为 HTML 标签。因此,不会创建新 HTML 标签,只会将其作为数据来处理。这也是为什么我们可以使用字符实体来避免用户不安全输入导致XSS的原因。

1、原始文本元素(Raw text elements)

在HTML中,属于 Raw text elements 的标签有两个:script、style。在 Raw text elements 类型标签下的所有内容块都属于该标签。

存在一条特性:Raw textelements类型标签下的所有字符实体编码都不会被HTML解码。HTML解析器解析到script、style标签的内容块(数据)部分时,状态会进入Script Data State,该状态并不在我们前面说的会解码字符实体的三条状态之中。

因此,<script>&#97;&#108;&#101;&#114;&#116&#40;&#57;&#41;&#59</script>这样字符实体并不会被解码,也就不会执行JS。

2、RCDATA

在HTML中,属于 RCDATA Elements 的标签有两个:textarea、title。RCDATA Elements 类型的标签可以包含文本内容和字符实体。解析器解析到 textarea、title 标签的数据部分时,状态会进入RCDATA State。前面我们提到,处于RCDATA State状态时,字符实体是会被解析器解码的。

示例:

<textarea>&#60;script&#62;alert(5)&#60;/script&#62;</textarea>
<>被编码为实体&#60;&#62;。
解析器解析到它们时会进行解码,最终得到<textarea><script>alert(5)</script></textarea>。
但是里面的JS同样还是不会被执行,原因还是因为解码字符实体状态机不会进入标签打开状态(Tag Open State),
因此里面的<script>并不会被解析为HTML标签

3、外部元素(Foreignelements)

Foreign elemnts 来源于 MathML 和 SVG 命名空间,<svg>遵循XML和SVG 的定义。

示例:

<script>alert&#40;1)</script>
不能弹窗,Raw text elements类型标签下的所有字符实体编码都不会被HTML解码
<svg><script>alert&#40;1)</script>
能弹窗,在XML中,&#40;会被解析成(,在XML中实体会自动转义,除了<[CDATA[和]]>包含的实体

JS 解析器

形如 \uXXXX这样的 Unicode 字符转义序列或 Hex 编码是否能被解码需要看情况。

首先,JavaScript 中有三个地方可以出现 Unicode 字符转义序列:

  1. 字符串中
    Unicode 转义序列出现在字符串中时,它只会被解释为普通字符,而不会破坏字符串的上下文。例如,<script>alert("\u0031\u0030");</script>被编码转义的部分为10,是字符串,会被正常解码,JS代码也会被执行。
  2. 标识符中
    若 Unicode 转义序列存在于标识符中,即变量名(如函数名等…),它会被进行解码。例如:<script>\u0061\u006c\u0065\u0072\u0074(10);</script>,被编码转义的部分为 alert 字符,是函数名,属于在标识符中的情况,因此会被正常解码,JS代码也会被执行。
  3. 控制字符中
    若 Unicode 转义序列存在于控制字符中,那么它会被解码但不会被解释为控制字符,而会被解释为标识符或字符串字符的一部分。控制字符即'、"、()等。例如,<script>alert\u0028"xss"); </script>,其中(进行了Unicode编码,那么解码后它不再是作为控制字符,而是作为标识符的一部分alert(。因此函数的括号之类的控制字符进行 Unicode 转义后是不能被正常解释的。

总结,Unicode序列不能出现在控制字符中,否则不能被解释。

示例1:

<script>\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029</script>
被编码部分为alert(11)。
该例子中的JS不会被执行,因为控制字符被编码了。

示例2:

<script>\u0061\u006c\u0065\u0072\u0074(\u0031\u0032)</script>
被编码部分为alert及括号内为12。
该例子中JS不会被执行,原因在于括号内被编码的部分不能被正常解释,要么使用ASCII数字,要么加""或''使其变为字符串,作为字符串也只能作为普通字符。

示例3:

<script>alert('13\u0027)</script>
被编码处为'。
该例的JS不会执行,因为控制字符被编码了,解码后的'将变为字符串的一部分,而不再解释为控制字符。
因此该例中字符串是不完整的,因为没有'来结束字符串。

示例4:

<script>alert('14\u000a')</script>
该例的JS会被执行,因为被编码的部分处于字符串内,只会被解释为普通字符,
不会突破字符串上下文。

示例5:

<img src="1" onerror=\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029>
无法执行
我们以浏览器的视角来看:首先读到<开始读取标签,然后读到onerror调用JS解析器。在JS中,单引号,双引号和圆括号等属于控制字符,
编码后将无法识别。所以对于防御来说,应该编码这些控制字符。

下面这种方式可以解析:
<img src="1" οnerrοr=\u0061\u006c\u0065\u0072\u0074('\u0031')>

可以结合上面的HTML编码
按照解析顺序反过去,先JS编码然后HTML解码
<img src="1" onerror=&#92;&#117;&#48;&#48;&#54;&#49;&#92;&#117;&#48;&#48;&#54;&#99;&#92;&#117;&#48;&#48;&#54;&#53;&#92;&#117;&#48;&#48;&#55;&#50;&#92;&#117;&#48;&#48;&#55;&#52;&#40;&#39;&#92;&#117;&#48;&#48;&#51;&#49;&#39;&#41;>
浏览器读到了<标签开始构造语法树,然后HTML解码,解码之后发现onerror于是进行一个JS解码,成功弹窗

延伸:
开发人员单纯的设置HTML实体编码为防御xss的手段,但是用户输入点在alert中
<img src = "https://text.com" onclick = 'alert("输入点")'>
如果用户正常输入的话凡是存在< ," 等都能被转码
攻击者可以通过语句 ");alert("test,在服务端被转码:
<img src = "https://gss1.bdstatic.com" onclick = 'alert("FIRST XSS&#34;&#41;&#59;&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#116;&#101;&#115;&#116;")'>
弹窗两次,是因为浏览器进行HTML解码发现存在两个alert()

所以对于这种情况,正确防御XSS的方法:
应该是先JavaScript编码然后再进行HTML编码
用户输入 ");alert("test 后在服务端先JavaScript编码然后再进行HTML编码
在
在浏览器端:
首先经过第一步HTML解码后变为\u0022\u0029\u003B\u0061\u006C\u0065\u0072\u0074\u0028\u0022\u0074\u0065\u0073\u0074
JavaScript解析器工作,变为 ");alert("test ,刚才已经讲过JavaScript解析时只有标识符名称不会被当做字符串,
控制字符仅会被解析为标示符名称或者字符串,因此\u0022被解释成双引号文本,\u0028和\u0029被解释成为圆括号文本,不会变为控制字符被解析执行。
在这里采用的先JS编码后HTML编码中只弹窗了一次。

URL解析器

通用URI的格式如下:

[协议名]://[用户名]:[密码]@[主机名]:[端口]/[路径]?[查询参数]#[片段ID]

URL 解析器也被建模为状态机,文档输入流中的字符可以将其导向不同的状态。首先,要注意的是 URL 的 Scheme 部分(协议部分)必须为 ASCII 字符,即不能被任何编码,否则URL解析器的状态机将进入No Scheme状态。
在这里插入图片描述
示例:

<a href="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>
URL编码部分的是javascript:alert(1)。
JS不会被执行,因为作为Scheme部分的"javascript"这个字符串被编码,导致URL解析器状态机进入No Scheme状态。

URL中的:也不能被以任何方式编码,否则URL解析器的状态机也将进入 No Scheme 状态。

<a href="javascript%3aalert(3)"></a>
由于:被URL编码为%3a,导致URL状态机进入No Scheme状态,JS代码不能执行。

示例:

<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;:%61%6c%65%72%74%28%32%29">
"javascript"这个字符串被实体化编码,:没有被编码,alert(2)被URL编码。
成功执行
首先,在HTML解析器中我们谈到过,HTML状态机处于属性值状态(Attribute Value State)时,字符实体时会被解码的,此处在href属性中,所以被实体化编码的"javascript"字符串会被解码。其次,HTML解析是在URL解析之前的,所以在进行URL解析之前,Scheme部分的"javascript"字符串已被解码,而并不再是被实体编码的状态。

解析顺序

  1. 首先浏览器接收到一个 HTML 文档时,会触发 HTML 解析器对 HTML 文档进行词法解析,这一过程完成 HTML 解码并创建 DOM 树。
  2. 接下来 JavaScript 解析器会介入对内联脚本进行解析,这一过程完成J S的解码工作。
  3. 如果浏览器遇到需要 URL 的上下文环境,这时 URL 解析器也会介入完成 URL 的解码工作,URL 解析器的解码顺序会根据 URL 所在位置不同,可能在 JavaScript 解析器之前或之后解析。但HTML解析总是第一步。

URL 解析和 JavaScript 解析,它们的解析顺序要根据情况而定。

示例1:

<a href="UserInput"></a>
该例子中,首先由HTML解析器对UserInput部分进行字符实体解码;
接着URL解析器对UserInput进行URL decode;
如果URL的Scheme部分为javascript的话,JavaScript解析器会再对UserInput进行解码。
所以解析顺序是:HTML解析->URL解析->JavaScript解析。

示例2:

<a href=# onclick="window.open('UserInput')"></a>
该例子中,首先由HTML解析器对UserInput部分进行字符实体解码;
接着由JavaScript解析器会再对onclick部分的JS进行解析并执行JS;
执行JS后window.open('UserInput')函数的参数会传入URL,所以再由URL解析器对UserInput部分进行解码。
因此解析顺序为:HTML解析->JavaScript解析->URL解析。

示例3:

<a href="javascript:window.open('UserInput')">
该例子中,首先还是由HTML解析器对UserInput部分进行字符实体解码;
接着由URL解析器解析href的属性值;
然后由于Scheme为javascript,所以由JavaScript解析;
解析执行JS后window.open('UserInput')函数传入URL,所以再由URL解析器解析。
所以解析顺序为:HTML解析->URL解析->JavaScript解析->URL解析。

综合实例:

<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;
&#x74;&#x3a;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;
&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;&#x31;
&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;
&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x36;&#x33;&#x25;&#x35;
&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;
&#x25;&#x33;&#x36;&#x25;&#x33;&#x35;&#x25;&#x35;&#x63;&#x25;
&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;
&#x37;&#x25;&#x33;&#x32;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;
&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;
&#x33;&#x34;&#x28;&#x31;&#x35;&#x29;"></a>

首先 HTML 解析器进行解析,解析到 href 属性的值时,状态机进入属性值状态(Attribute Value State),该状态会解码字符实体。接着由URL解析器进行解析并解码,再接着由于 Scheme 为 javascript,因此由 JavaScript 解析器解析并解码,加上编码部分是函数名,属于标识符,因此可以正常解码解释。经过三轮解析解码后得到结果:<a href="javascript:alert(15)"></a>

总结

本文参考文章:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tr0e

分享不易,望多鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值