HTML 的纷争

HTML 语言的混乱主要源于四种主要解析引擎各自为政,IE 的Trident引擎,Firefox 的Gecko ,Safari 和Chrome 的Webkit,Opera 的Presto.

HTML 文档的基本概念

语法

HTML 是标签组成的层级结构,文本穿插其中,而主要是使用无个字符进行约束和限制,左右尖括号,单引号,双引号,&,所以说,HTML 里会有一些针对这些字符的规则:

以上几条规劝,实际上就是在告诫这样书写的代码,随时会被攻击者抓住,利用代码的缺陷,构造XSS 或者是SQL注入。

文档解析模式

对于传统的HTML,解析器会修复大部分语法上的不合规,同时不区分大小写,参数值不一定要用括号括起来,某些标签可以隐式闭合,而对于XML 来说,标签必须严格匹配,区分大小写,可以整合其他兼容格式内容。

值得一提的是,对于HTML ,碰到某些标签后,会落入特殊模式,直到出现特定的终止字符才会退出状态。如style, script, textarea, xmp 直到出现反斜杠匹配,才会退出。

而对于XML,禁止出现单个「<」和「&」,还有一个特殊的语法,只要一”<![CDATA[“字符串开头,以”]])”结束的,中间短路可以封装含有人以标签的任意原始文本数据。

语义之争:Tim Berners-Lee 的语义网梦想,这里还是比较遥远,不做进一步探讨。

HTML 解析器的行为

对于XML 来说,不需要担心,因为解析器的错误几乎零容忍。但是,对于HTML 来说,简直就是灾难,为了最大程度的解析,HTML 会以各种让人摸不着头脑的大胆的方式猜测网页作者的用途。以下边这个标签为例:

先不考虑它没有闭合仍然正常被解析这件事,看各家解析器做出的各种支持:

一个常见的攻击手段就是xss,此处借助title,我们将 hello world 替换成 hello world” onerror=”alert(1) 于是,对于原标签就变成了:

1
<img src=image.jpg tile="hello world"onerror="alert(1)" class=example>

很自然的一个脚本被我们植入进去了,一个xss 就被触发了。

多重标签的交互

在不正常的HTML 里,还会出现多个HTML 标签堆叠在一起,看起来就让人头疼,而浏览器在解析的时候,也会有区别,比如:

1
<i <b>

大多数浏览器会先解析成‘< i >’ ,而把< b 视为无效的标签参数。

另外,整个文档结束时候标签未闭合的情况也让人摸不清头脑。比如:

1
<i foo="<b>" [EOF]

大多数浏览器解析为i 的标签,或者是整个忽略掉,但对于IE 和 Opera 却从后往前处理,把这一串理解成 b 的标签。

所以假如攻击者,可以阶段加载的页面,就可以通过构造手段,让浏览器解析成完全不同的内容。

HTML 实体编码

(由于MarkDown语言里,自动把这些编码识别出来了,包括尖括号,只好中间加空格或这种方式来书写。)

hTML 的实体编码格式是以&开头,以分号结尾,在HTML 规范里,散步着无数这样的命名实体,比如& lt; 插入左尖括号,& gt;用于插入右尖括号,& amp; 替换 &符号自身。& rarr; 代表一个Unicode箭头等等。

除了命名实体,还可以插入任意十进制ASCII 或者 Unicode 字符编码,样式是 &#数字;
例如 &# 60; 被识别成左尖括号,&# 62; 别识别为右尖括号。而十六进制的标记符在这在编码前边加一个x,所以 &# x3c; 仍然是一个左尖括号。

对于HTML 来说,解析器能识别在文本节点和参数值里边的实体编码,在创建文档树的时候,透明的对这些编码进行解码。

但是有一点疑惑的是,在识别和解析HTML 实体任务重,有一些奇怪的解析,比如传统解析中,只要实体名称后边跟着的字符不是字母数字,即使是少了分号,实体名还是被接受了。对于数字型的实体,后边可以跟任意多个0,导致一个超长的数字串,会造成出错。

HTTP/HTML 交互语义

一般来说,我们会在HTTP 的头域指定内容的一些信息,比如Content-Type, Content-Disposition, Transfer-Encoding等等,然而考虑如果我们不适用HTTP 传输HTML,甚至是直接从本地加载HTML,就不能依靠HTTP 的头域来确定编码等信息了。如果缺了像 MIME type 或者字符集这样关键参数,就会破事浏览器随意处置文档的编码解析问题。

解决办法就是文档开头声明< meta http-equiv=…> 指令进行编码设定,一个典型的指定是这样的:

1
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">

如果多个http-equiv冲突,或者从服务器端返回HTTP 头域有冲突的话,浏览器表现就会很不一致,一般优先支持hTTP 响应头,然后支持第一个出现的charset。

超链接与内容包含

这一块是XSS 的重灾区。

一个单纯的HTML 链接写法是这样的:

1
<a href="http://www.example.com/">click me!</a>

对于这个超链接,它支持指向浏览器支持的所有协议,包括伪URL,如data: javascript:。

同时,该语法可选的target参数有四种,_blank总是打开新窗口;_parent 转到包含发出当前链接文档的上一级视图;_top 转到浏览器最顶级窗口。_self 和没有设置这个值一样。

表单和表单触发的请求

XSS 重灾区。

一个典型的表单写法如下:

1
2
3
4
5
6
<form method=GET action="/process_form.cgi">
名字:<input type=text name=given>
姓氏:<input type=text name=family>
...
<input type=submit value="完成后提交">
</form>

值得注意的是,form 彼此不能嵌套,如果嵌套了,只有最外层的有效。

如果method 为GET,那么包含的字段名称和他们的值,会以百分号编码机制转义,但其中空格(0x20) 会被以加号代替而非%20,而所有的加号被编码成%2b 。经过编码的「名称=值」数据对用& 连接付分割,组成完整的字符串。

如果method 是POST,分几种情况:

Frame 框架

其作用是使HTML 文档内嵌和显示另一个页面中,有独立的文档视图,甚至独立的JS 环境。对于src 值的限定,与其他链接的规则基本相同, 所以 iframe 也是一个安全隐含。

其他特定的内容

包括图片,层级样式表,客户端脚本,插件内容等等,这些不再赘述,他们都有XSS 的风险。

安全工程


HTML 部分写完,但是要想完全搞清楚HTML 编码部分的问题,简直是一件不可能完成的任务,曾经有本书叫《Web Application Obfuscation》,它企图创建出能拦截所有已知的危险模式的过滤器,同时又不会影响到其他段落的正常功能,而实际上,这是不可能完成的任务。

一个良好的方法是用一个解析器,把输入的文档翻译成放在内存里的层级文档树,然后去除掉那些无法识别的参数和标签,和所有不需要的标签、参数、参数值。然后在对文档树进行良好的排序和转义,这样看起来要方便和清晰许多。

另外,HTML 中存在问题,造成最大的影响就是XSS 和CSRF 这两个攻击手段,攻击者会绞尽脑汁的绕过开发者设置的层层障碍,他们的最终目的无论如何,都是试图通过各种混淆的代码,让解析器理解成其他意思,达成攻击效果。而XSS 的三种攻击模式,反射型,储存型,DOM 型,都是在这些修修补补的篱笆上找一个足以穿越过去的漏洞。

在配合上HTTP 的问题,可以创造一切可能,对于XSS 的世界,脑洞有多大,可能有多大。

script>