HTML 语言的混乱主要源于四种主要解析引擎各自为政,IE 的Trident引擎,Firefox 的Gecko ,Safari 和Chrome 的Webkit,Opera 的Presto.
语法
HTML 是标签组成的层级结构,文本穿插其中,而主要是使用无个字符进行约束和限制,左右尖括号,单引号,双引号,&,所以说,HTML 里会有一些针对这些字符的规则:
以上几条规劝,实际上就是在告诫这样书写的代码,随时会被攻击者抓住,利用代码的缺陷,构造XSS 或者是SQL注入。
文档解析模式
对于传统的HTML,解析器会修复大部分语法上的不合规,同时不区分大小写,参数值不一定要用括号括起来,某些标签可以隐式闭合,而对于XML 来说,标签必须严格匹配,区分大小写,可以整合其他兼容格式内容。
值得一提的是,对于HTML ,碰到某些标签后,会落入特殊模式,直到出现特定的终止字符才会退出状态。如style, script, textarea, xmp 直到出现反斜杠匹配,才会退出。
而对于XML,禁止出现单个「<」和「&」,还有一个特殊的语法,只要一”<![CDATA[“字符串开头,以”]])”结束的,中间短路可以封装含有人以标签的任意原始文本数据。
语义之争:Tim Berners-Lee 的语义网梦想,这里还是比较遥远,不做进一步探讨。
对于XML 来说,不需要担心,因为解析器的错误几乎零容忍。但是,对于HTML 来说,简直就是灾难,为了最大程度的解析,HTML 会以各种让人摸不着头脑的大胆的方式猜测网页作者的用途。以下边这个标签为例:
先不考虑它没有闭合仍然正常被解析这件事,看各家解析器做出的各种支持:
一个常见的攻击手段就是xss,此处借助title,我们将 hello world 替换成 hello world” onerror=”alert(1) 于是,对于原标签就变成了:
|
|
很自然的一个脚本被我们植入进去了,一个xss 就被触发了。
多重标签的交互
在不正常的HTML 里,还会出现多个HTML 标签堆叠在一起,看起来就让人头疼,而浏览器在解析的时候,也会有区别,比如:
|
|
大多数浏览器会先解析成‘< i >’ ,而把< b 视为无效的标签参数。
另外,整个文档结束时候标签未闭合的情况也让人摸不清头脑。比如:
|
|
大多数浏览器解析为i 的标签,或者是整个忽略掉,但对于IE 和 Opera 却从后往前处理,把这一串理解成 b 的标签。
所以假如攻击者,可以阶段加载的页面,就可以通过构造手段,让浏览器解析成完全不同的内容。
(由于MarkDown语言里,自动把这些编码识别出来了,包括尖括号,只好中间加空格或这种方式来书写。)
hTML 的实体编码格式是以&开头,以分号结尾,在HTML 规范里,散步着无数这样的命名实体,比如& lt; 插入左尖括号,& gt;用于插入右尖括号,& amp; 替换 &符号自身。& rarr; 代表一个Unicode箭头等等。
除了命名实体,还可以插入任意十进制ASCII 或者 Unicode 字符编码,样式是 &#数字;
例如 &# 60; 被识别成左尖括号,&# 62; 别识别为右尖括号。而十六进制的标记符在这在编码前边加一个x,所以 &# x3c; 仍然是一个左尖括号。
对于HTML 来说,解析器能识别在文本节点和参数值里边的实体编码,在创建文档树的时候,透明的对这些编码进行解码。
但是有一点疑惑的是,在识别和解析HTML 实体任务重,有一些奇怪的解析,比如传统解析中,只要实体名称后边跟着的字符不是字母数字,即使是少了分号,实体名还是被接受了。对于数字型的实体,后边可以跟任意多个0,导致一个超长的数字串,会造成出错。
一般来说,我们会在HTTP 的头域指定内容的一些信息,比如Content-Type, Content-Disposition, Transfer-Encoding等等,然而考虑如果我们不适用HTTP 传输HTML,甚至是直接从本地加载HTML,就不能依靠HTTP 的头域来确定编码等信息了。如果缺了像 MIME type 或者字符集这样关键参数,就会破事浏览器随意处置文档的编码解析问题。
解决办法就是文档开头声明< meta http-equiv=…> 指令进行编码设定,一个典型的指定是这样的:
|
|
如果多个http-equiv冲突,或者从服务器端返回HTTP 头域有冲突的话,浏览器表现就会很不一致,一般优先支持hTTP 响应头,然后支持第一个出现的charset。
这一块是XSS 的重灾区。
一个单纯的HTML 链接写法是这样的:
|
|
对于这个超链接,它支持指向浏览器支持的所有协议,包括伪URL,如data: javascript:。
同时,该语法可选的target参数有四种,_blank总是打开新窗口;_parent 转到包含发出当前链接文档的上一级视图;_top 转到浏览器最顶级窗口。_self 和没有设置这个值一样。
表单和表单触发的请求
XSS 重灾区。
一个典型的表单写法如下:
|
|
值得注意的是,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 的世界,脑洞有多大,可能有多大。