编码问题
0x01 编码发展史
- 0x20以下的编码用作控制码,接下来一直到127号的的字节用来表示大小写字母,标点符号,空格,数字,这就是ascii码,只能满足美国人的需要。
- 中国人民通过对 ASCII 编码的中文扩充改造,产生了 GB2312 编码,可以表示6000多个常用汉字。
- 汉字实在是太多了,包括繁体和各种字符,于是产生了 GBK 编码,它包括了 GB2312 中的编码,同时扩充了很多。
- 中国是个多民族国家,各个民族几乎都有自己独立的语言系统,为了表示那些字符,继续把 GBK 编码扩充为 GB18030 编码。
- 每个国家都像中国一样,把自己的语言编码,于是出现了各种各样的编码,如果你不安装相应的编码,就无法解释相应编码想表达的内容。
- 终于,有个叫 ISO 的组织看不下去了。他们一起创造了一种编码 UNICODE ,这种编码非常大,大到可以容纳世界上任何一个文字和标志。所以只要电脑上有 UNICODE 这种编码系统,无论是全球哪种文字,只需要保存文件的时候,保存成 UNICODE 编码就可以被其他电脑正常解释。
- UNICODE 在网络传输中,出现了两个标准 UTF-8 和 UTF-16,分别每次传输 8个位和 16个位。
- 于是就会有人产生疑问,UTF-8 既然能保存那么多文字、符号,为什么国内还有这么多使用 GBK 等编码的人?因为 UTF-8 等编码体积比较大,占电脑空间比较多,如果面向的使用人群绝大部分都是中国人,用 GBK 等编码也可以。但是目前的电脑来看,硬盘都是白菜价,电脑性能也已经足够无视这点性能的消耗了。所以推荐所有的网页使用统一编码:UTF-8。
0x02 各种编码
- URL编码
一个百分号和该字符的ASCII编码所对应的2位十六进制数字,例如“/”的URL编码为%2F(一般大写,但不强求)
具体情况:
网址路径的编码,用的是utf-8编码
查询字符串的编码,用的是操作系统的默认编码
GET和POST方法的编码,用的是网页的编码
在Ajax调用中,IE总是采用GB2312编码(操作系统的默认编码),而Firefox总是采用utf-8编码。
escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值。比如"春节"的返回结果是%u6625%u8282,也就是说在Unicode字符集中,"春"是第6625个(十六进制)字符,"节"是第8282个(十六进制)字符。
它的具体规则是,除了ASCII字母、数字、标点符号"@ * _ + - . /"以外,对其他所有字符进行编码。在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数是unescape()。
所以,"Hello World"的escape()编码就是"Hello%20World"。因为空格的Unicode值是20(十六进制)。
- HTML实体编码
命名实体:以&开头,分号结尾的,例如“<”的编码是“<”
字符编码:十进制、十六进制ASCII码或unicode字符编码,样式为“&#数值;”,例如“<”可以编码为“<”和“<”
- JS编码
js提供了四种字符编码的策略,
三个八进制数字,如果不够个数,前面补0,例如“e”编码为“\145”
两个十六进制数字,如果不够个数,前面补0,例如“e”编码为“\x65”
四个十六进制数字,如果不够个数,前面补0,例如“e”编码为“\u0065”
对于一些控制字符,使用特殊的C类型的转义风格(例如\n和\r)
- CSS编码
用一个反斜线()后面跟1~6位的十六进制数字,例如e可以编码为“\65”或“65”或“00065”
- 复合编码
所谓复合编码,也就是说输出的内容输出在多个环境中,例如
<td onclick=”openUrl(add.do?userName=’<%=value%>’);”>11</td>
value的内容首先出现在一个URL中,这个URL在一段javascript总,而javascript代码又是html的一部分。所以解码的顺序就是HTML解码–>js解码–>url解码,那么正确的编码顺序就应该是url编码–>js编码–>html编码。
0x03 浏览器解析
- 浏览器解析HTML的步骤
浏览器收到从服务器发送来的HTML内容,会从头解析,当遇到<script></script>时,会调用javascript脚本解析器解析javascript,并执行脚本,然后继续解析其他的HTML内容,对于一些需要触发才能执行的事件,当触发事件发送时,脚本解析器才会解析其中的脚本,在事件触发之前,它是HTML的一部分。
- 编码问题实例解析分析
1、URL编码"javascript:alert(1)"
<a href="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>
Answer: The javascript will NOT execute.
url不能对协议以及协议后的冒号进行编码,否则url解析器会认为其是无类型的。这里JavaScript是个伪协议,而且对javascript进行了url编码,会导致url解析器认为其是无类型,无法进行url解码。最终也就不会弹窗。
2、HTML字符实体编码 "javascript" 和 URL 编码 "alert(2)"
<a href="javascript:%61%6c%65%72%74%28%32%29">
Answer: The javascript will execute.
这里javascript属于html解析中的”属性值中的字符引用“,所以可以先被html解码,然后被url解析器识别出来,后面的alert(2)由于不是协议,url解析器可以正常解析,最后会弹窗。
3、URL 编码 ":"
<a href="javascript%3aalert(3)"></a>
Answer: The javascript will NOT execute.
url不能对协议以及协议后的冒号进行编码,否则url解析器会认为其是无类型的。这里JavaScript是个伪协议,而且对javascript后的冒号进行了url编码,会导致url解析器认为其是无类型,无法进行url解码。最终也就不会弹窗。
4、HTML字符实体编码 < 和 >
<div><img src=x onerror=alert(4)></div>
Answer: The javascript will NOT execute.
“<”和“>”字符被编码为“<”和“>”。当解析器解析完“<div>”并处于“数据状态”时,这两个字符将会被解析。当解析器遇到“&”字符,它会知道这是“数据状态的字符引用”,因此会消耗一个字符引用(例如“<”)并释放出对应字符的token。在这个例子中,对应字符指的是“<”和“>”。读者可能会想:这是不是意味着“<”和“>”的token将会被理解为标签的开始和结束,然后其中的脚本会被执行?答案是脚本并不会被执行。原因是解析器在解析这个字符引用后不会转换到“标签开始状态”。正因为如此,就不会建立新标签。因此,我们能够利用字符实体编码这个行为来转义用户输入的数据从而确保用户输入的数据只能被解析成“数据”。
5、HTML字符实体编码 < 和 >
<textarea><script>alert(5)</script></textarea>
Answer: The javascript will NOT execute AND the character entities will NOT be decoded either
在<textarea>和<title>标签中的字符引用会被HTML解析器解码,在解析这些字符引用的过程中不会进入“标签开始状态”,甚至在<textarea>和<title>的内容中都无法创建标签,更不会有javascript执行了。
6、<textarea>和<title>
<textarea><script>alert(6)</script></textarea>
Answer: The javascript will NOT execute.
在浏览器解析RCDATA元素的过程中,解析器会进入“RCDATA状态”。在这个状态中,如果遇到“<”字符,它会转换到“RCDATA小于号状态”。如果“<”字符后没有紧跟着“/”和对应的标签名,解析器会转换回“RCDATA状态”。这意味着在RCDATA元素标签的内容中(例如<textarea>或<title>的内容中),唯一能够被解析器认做是标签的就是“</textarea>”或者“</title>”。当然,这要看开始标签是哪一个。因此,在“<textarea>”和“<title>”的内容中不会创建标签,就不会有脚本能够执行。
7、HTML字符实体编码 " ' " (单引号)
<button onclick="confirm('7');">Button</button>
Answer: The javascript will execute.
html先将'解码为一个控制字符',闭合成功,然后javascript进行解析,最终执行。
8、Unicode编码 " ' " (单引号)
<button onclick="confirm('8\u0027);">Button</button>
Answer: The javascript will NOT execute.
javascript把uniocde\u0027解析为一个常量',而不是控制字符',所以无法闭合。
//像圆括号、双引号、单引号等等这些控制字符的unicode编码,在进行JavaScript解析的时候仅会被解码为字符串文本或者标识符名称
9、HTML字符实体编码 alert(9);
<script>alert(9);</script>
Answer: The javascript will NOT execute.
在script块中的字符引用并不会被解析和解码,对于unicode字符视情况而定。
10、Unicode 编码 alert
<script>\u0061\u006c\u0065\u0072\u0074(10);</script>
Answer: The javascript will execute.
Unicode转义序列只有在标识符名称里不被当作字符串,也只有在标识符名称里的编码字符能够被正常的解析。当Unicode转义序列出现在标识符名称中时,它会被解码并解释为标识符名称的一部分,比如标识符alert,所以可以弹框。
11、Unicode 编码 alert(11)
<script>\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029</script>
Answer: The javascript will NOT execute.
这里的unicode转义序列 (11) 不是标识符,所以无法正常解析,即无法弹窗。
12、Unicode 编码 alert 和 12
<script>\u0061\u006c\u0065\u0072\u0074(\u0031\u0032)</script>
Answer: The javascript will NOT execute.
\u0031\u0032不会被解释为字符串常量(因为它们没有用引号闭合)
13、Unicode 编码 " ' " (单引号)
<script>alert('13\u0027)</script>
Answer: The javascript will NOT execute.
这里\u0027没有被解析为控制字符,无法闭合,所以无法弹窗。
(当用Unicode转义序列来表示一个控制字符时,例如单引号、双引号、圆括号等等,它们将不会被解释成控制字符,而仅仅被解码并解析为标识符名称或者字符串常量。)
14、Unicode 编码换行符(0x0A)
<script>alert('14\u000a')</script>
Answer: The javascript will execute.
这里会执行程序并弹窗,因为\u000a被解析成了换行字符文本,避免了因为换行符的出现而导致的javascript语法错误。
15、混合加密解析
<a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)"></a>
Answer: The javascript will execute.
(1)先经过html解析(题目2,属性值中的字符引用,正常解析)
<a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)"></a>
(2)然后url解析(依旧题目2,url解析的内容为非协议内容,正常解析)
<a href="javascript:\u0061\u006c\u0065\u0072\u0074(15)"></a>
(3)unicode解析(题目10,unicode转义字符出现在标识符名称中,正常解析)
<a href="javascript:alert(15)"></a>
(4)最后语法正确,javascript正常解析并弹窗。(当然,这里需要在</a>前面随便加点字符,不然无法显示并弹窗)
资料索引:
对于xss等有关的html,url,unicode编码做的一个小总结
深入理解浏览器解析机制和XSS向量编码
浏览器编码流程
從XSS理解編碼解析原理
网页编码就是那点事