CSS和JavaScript的烦恼

CSS

CSS 是能够做到网页表现和内容分离的样式设计语言,有人爱之深切,有人恨之入骨。由于CSS 的存在,让HTML 在语义学上越来越不重要,你随便打开一个网页,翻看一下源码,就会发现,满屏幕的div, 只起到了结构的作用,Tim Berners-Lee 的语义网梦想被CSS 阻挡,因为即使全屏的div,css 仍然能通过他五花八门的指令,伪类选择器,伪函数等等实现一个美观的网页。

当然,这里谈CSS, 并不是讲切图的,而是在CSS 里,同样存在着种种漏洞,主要是以XSS 为主的漏洞,所以对CSS 的探讨也在所难免。

基本语法

一般使用方法有直接在< style >中使用,能够全局生效。或者通过< link rel=stylesheet > 引入外部的css 文件。或者是直接为某个标签赋予style参数。

选择器,包括复杂点的伪类选择器这里就不说了,只要稍微用过的人都知道,或者去W3C shcool也能看到详细的介绍。在CSS 的规则里,属性值是其最重要的东西,大约格式有三种:

@指令和 XBL绑定

除了上边所说的选择器,属性功能,CSS 还支持一种以@ 开头的格式,允许导入独立样式,同时能够改变样式表各种设定,比如指定命名空间,设定不同的显示媒体使用的不同的样式表(如今的跨设备就是如此)。

有两个重要的指令:

关于XBL 绑定,全称叫XML Binding Language,参见XBL维基百科用于描述如何将其他文档中的元素(如XPCOM的功能)绑定到部件(如XUL部件)。可以使用级联样式表(CSS)或文档对象模型(DOM)两种方式绑定,绑定可为部件创建新的行为。绑定可以包含已注册到“被绑定元素”的事件处理程序,实现对“被绑定元素”的新的方法和属性访问,包括“被绑定元素”内的“匿名内容”。

该语言现在算是Mozilla 专有,FireFox 中的一个特性,利用 -moz-binding ,可以从外部源载入外部内容指令,可包括一些JavaScript代码。目前该使用方式并不明朗,当然看起来其中也有着不小的XSS 风险。

值得注意的是,上边提到的@import, url() 都存在有引入伪URL 的安全隐患,大多数浏览器都不接受CSS 的上下文环境使用脚本,但是IE 6 却支持,可以想象,IE6 还存在有大量漏洞,而在中国这片国土,IE 6仍然有不小的占有量。

交互过程

之前说,CSS 和HTML 是各自独立的,也就是说在执行过程中,会先解析HTML,然后解析CSS,但是,如果在CSS 属性里包含了某些HTML 语法,就会造成问题,比如:

1
2
3
4
5
<style>
some_descriptor {
background: url('http://www.example.com/</style><h1>hi!');
}
</style>

如此这般,即使是放在了引号里,html对style 的解析也会提前结束,造成问题。

重新同步的风险

很明显的CSS 的发展之路,深受HTML 的影响,所以在CSS 的解析上,和HTML 一样存在着许多让人摸不清头脑的问题,比如,解析器碰到错误时,仍会继续工作直到碰到下一对匹配的尖括号或大括号括号恢复正常解析,比如:

1
2
3
4
5
6
a {
fklj@#$%@$@!
}
img {
border: 1px solid red;
}

尽管img 前边的解析的是错误的,仍然会正常解析img。利用这种特性,我们很容器就能想起一些攻击方式,比如对对某些浏览器输入一些有效的输入,对另一些浏览器,却能造成CSS 错误,利用CSS 解析器对出错CSS 的重新同步处理,到能导致一些特别的攻击行为诞生。

举个栗子,比如IE 的浏览器对CSS多行字符串文本支持,所以如果用户提供的CSS 字符串里边包含CR和LF 换行符的话,IE 是OK 的,所以网站开发者,不会对此进行过滤,但是对其他浏览器确实错误的,于是就导致了前边解析错误,只能解析后边恶意植入的代码:

1
2
3
4
some_selector {
content: 'Attacker-controller text...
} evil_rule {margin-left: -1000px;}';
}

对于其他浏览器,由于前边的解析错误,css 会跳到恶意代码那里判定为正常代码执行。最简单的解决办法就是禁止它,不要对IE 姑息。

另外一些存在的问题是,CSS 之间兼容的we附体,比如一些老版本的解析不明白新版本的内容,比如CSS3 里的中括号,不过现在好像CSS3以前的版本已经绝迹,在此就不再赘述了。

又见字符编码

字符编码似乎是围绕了整个Web 的问题,毕竟Web 是一个以内容交流为主要功能的平台,各种编码在所难免,问题也在所难免。

在CSS 中,为了在CSS字符串中使用一些保留字符或者有问题的字符,CSS 提僧了不太正统的策略。它使用反斜杠\ 后边跟1~6位十六进制数字的方式,再这样的策略下,比如字母e, 可以编辑成\65,\065,\000065, 一般来说,只有最后一种方法才不会产生歧义,比如说teak,编码成 t\65ak,就会转义成\65a。而css 避免这种情况的方法,不过是在每个转义序列后边加上空格。

存在安全问题是,很多CSS 解析器 竟然能够接受未被引号括起来的字符串里的任意转义序列,特别的在IE 中,转义的优先级还要高于伪函数语法解析,下边这样的结果是一样的:

1
2
color: expression(alert(1))
color: express\028 alert \028 1 \029 \029

甚至在IE 中,url() 中的反斜杠并不会解析成转义符,这仅仅是照顾那些URL 中输错了斜杠的用户的的感情~~~

CSS 安全工程


CSS 可以说完全吸取了 HTML 的混乱风格,在整个CSS 发展之路上,充满了风险。当然,CSS 所能造成的灾难在明面上看起来,相比于HTML 少很多,但是仍然不能小觑,因为配合着社工,黑客仍然能够建立起一次可怕的入侵。

浏览器脚本

JavaScript 基本特点

JavaScript 的发展之路也是充满了崎岖,不再赘述,JavaScript 本身是一门相当简单的运行时解释语言,它的语法受到C 的影响,没有类的概念,有自动垃圾回收,有弱数据类型和动态类型的特点。

脚本处理模型

前边也说了,对于JavaScript执行环境来说,就像一个沙箱,对每个在浏览器中的HTML 文档,都被赋予了独立的JavaScript 执行环境这些加载脚本的所有全局变量和函数都有一个独立的命名空间。

当然,看起来脚本隔离的规则非常的严格,跨文档的交互也必须非常显式的方式进行,和操作系统的进程隔离非常相似,不过其设计的范围还是远比进程隔离小,所以仍然会存在有一些可以利用的漏洞。

模型的处理流程

源码处理

主要是检查脚本代码块里的语法,转换成中间层的二进制映像。在完成这一步骤之前,这些二进制代码不会对全局才造成影响,如果该阶段出错,那么整段问题代码都会被抛弃,继续解析下一段代码块。注意代码块之间除非正常解析,才会拼接在一起。

函数解析
完成了源代码处理的流程,解析器会对当前代码块里的所有具名的全局函数进行识别并注册。该阶段完成后,函数才会被执行代码调用。

注意每段独立的代码块并不是同时处理的,而是根据JavaScript 引擎读取代码块的先后顺序决定的,所以下边这样的写法是失败的:

1
2
3
4
5
6
7
8
<script>
hello();
</script>
<script>
function hello() {
alert("hello");
}
</script>

而下边这个写法是能够成功执行的,因为对hello() 的注册要先于第一行代码。

1
2
3
4
5
6
7
<script>
hello();
function hello() {
alert("hello");
}
</script>

然而这样的全局名称解析模型只对函数有效,对变量却并非如此,和其他脚本语言类似,变量是按照执行出现的顺序注册的,所以下面的例子是错误的:

1
2
3
4
5
6
7
<script>
hello();
var hello = function() {
alert("hello");
}
</script>

代码执行

由于JavaScript的异步性,在执行过程中,如果碰到错误,那些已经被正确解析的函数仍然能被调用,而且已经执行的代码产生的结果,对上下文仍然有效。

执行顺序的控制

在同一个执行环境里,JavaScript 是按照时间顺序执行的,外部事件无法中断代码的运行,也不支持线程对任何共享内存的修改。大多数情况下,JavaScript执行时,整个浏览器至少HTML 渲染器部分基本处于不响应状态。

更进一步的说,JavaScript本身没有sleep(),或者pause() 这种暂停功能来释放CPU,如果希望延迟执行,需要注册一个定时器来延迟。而定时器里,也可以写一些内嵌的JavaScript代码。

任何死循环都会被中断退出,等价于一个未处理的异常。循环退出,引擎恢复到闲置状态,引起问题的代码仍然可以被调用,所有的计时器和事件句柄也会保持原样。

代码和对象检视功能

检视功能

对代码来说,对于非内置函数,可以通过toString() 和 toSource() 方法可以查看反编译后的源码。

对于程序运行流来说,没有多好的办法能够查看,勉强搜索本页的script代码等,但一般无法知道运行到或者是将执行到哪,在此推荐调试利器Firebug。

eval()

对于eval 中的文本语法错误或执行异常都会传递给调用eval 的函数。也就是说,语法解析无误后,执行过程中产生的未处理异常也会传递到调用 eval 的函数。如果没有问题,最后一行代码的执行结果将是eval 的返回值。

延迟执行

有多种机制可以实现延迟执行,包括定时器:setTimeout, setInterval;事件处理器: onclick, onload;HTML解析器自身的若干接口:innerHTML , document.write等。

自省

JavaScript的自省相对完备,可以使用常见的迭代器方式,也可以通过typeof, instanceof 或者全等符 === 或者length这样的属性获得额外信息。

修改运行环境

重写内置函数
如果任由流氓脚本发挥,可做的坏事很多,删除,重写,或者屏蔽大部分JavaScript内置函数和所有浏览器的提供的I/O 方法。

1
2
eval = alert;
eval("hi");

这样的代码是没有问题的,但是在执行之后,Chrome,Safari,Opera 都会通过delete 操作符,删掉整个eval()函数,因为你已经重写了内置函数。

从此延伸开去,几乎所有的对象,包括内置对象,String,Array,都有一个能被任意修改的原型。这个原型是个master 对象,已产生的全体对象实例甚至包括还未产生的实例,方法和属性都衍生自这个主题。(此处是需要进一步学习的点)

Setter Getter
尽管他们不如C++ 里的运算符重载那么强大,但是这两个对象足以使得已有对象或者对象原型的行为变得困惑。

1
2
3
4
5
6
7
var evil_object = {
set foo() { alert("hi"); },
get foo() { return 2; }
};
/* 执行以下代码只会显示 hi.*/
evil_object.foo = 1;
/* 而此时,foo 并不等于1 */

JavaScript 存在的潜在风险是,在特定的上下文环境里执行脚本,一旦受到了非新人内容的干扰,就没有可靠办法来检查其运行环境是否正确了。比如如果盲目信任了 location 对象,就可能导致一系列的漏洞。

JSON

关于JSON的话题很多,JSON 是对大括号的含义进行了重载,意味着JSON 区块里的内容不能按照独立的代码语句来执行。这样就可以防止< script src=… > 这样的方式进行跨站引用。

但是JSON 还是存在有注入风险,其中一个风险来自于eval, 有些开发者往往直接使用eval 直接来吧json 转换成js 对象,如果此时JSON 数据包中注入了恶意的数据,则直接就获得了执行。所以,正确的做法是应当分割出JSON 里包含的特殊字符,然后再解析为对象,比如下面,json2.js里的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');

JSON 得到了最普遍应用,另外预支竞争的有JSONP,JSONP 字面含义是填充式(padding)的JSON,它通过填充额外的内容把JSON 序列化包装起来,变成一段有效的可独立运行的JavaScript于珊珊,常见栗子包括函数调用(如 callback_funcition({…JSON data…})) 或者变量赋值(var return_value = {…json data…})), 但是这些序列化无法兼容JSON。parse(),必须使用不安全的eval.

而JSONP 这种的特点是使用第三方页面以< script src=… >形式加载这些数据时,解析不会出现错误。当然,这种情况实际上还会带来许多风险,《Web 之困》中的说明太过简单,在JSON Hijacking 有更为详细的解读,以及这篇闲扯Web安全之JSON,对于其中的详细知识,还有一些漏洞,后边会继续扩充。

总的来说,注意这么几点:

标准对象层级

JavaScript 的运行环境都是围绕一个隐含的根对象构建起来的,这个根对象也是JavaScript程序里所有全局变量和函数的默认命名空间。

JavaScript 预设了一些层级模式的函数,用来实现浏览器环境里的输入输出功能,包括:对浏览器窗口的操作(open(),close(),moveTo(),resizeTo(),focus(),blur()等), JavaScript 定时器设置(setTimeout(), setInterval()等),各种提示(alert(),prompt(),print()),以及一些浏览器开发商支持的函数等等。

同时,顶级对象还提供了上下文环境对象的JavaScript引用,比如父框架parent,顶层文档top, 当前窗口的源窗口opener,当前文档的子框架 frames[],以及window和self。

顶级的对象结构里,除了Document 还有一些子对象,如下:

DOM 东西太多,一言难尽,其中包括转义方面的问题,注入的漏洞。等另开一篇文章讲。

脚本字符编码

JavaScript 支持很多常见的反斜杠方式的字符串编码,用于转义引号,HTML 标记 和内嵌在文本中的有问题的字符。

对于最后一条 值得注意的是,因为JavaScript 解析顺序要晚于HTML 解析,所以,不应该用这种方式来转义尖括号,和其他HTML 语法中的分隔符,这样HTML 解析完毕,再解析JavaScript,转义结束之后就会造成问题。这和CSS 问题一样。

同时以上各种方式,只有Unicode 转义方式可以用在字符串之外的位置,其他转义则不可以,而且在JavaScript里,转义编码只能出现在标示符部分,不能用在对语法有真正影响的符号上,比如括号。

1
\u006lert("hello");

上边写法是ok的,但是如果用这种方式替换圆括号或者是引号,就会失败。

JavaScript 代码包含在哪里

JavaScript 代码经常包含在哪里呢?

这也是所有可能发生漏洞的地方,比如定时器那里,考虑以下代码:

1
2
3
4
5
<script>
var value = "user_string";
...
setTimeout("do_stuff('"+value+"')", 1000);
</script>

表面上看他没有问题,对 value 只做一次转义就好了,但实际呢,考虑其解析过程,首先是HTML 解析出script 块,然后JavaScript 做第一次解析,检查setTimeout 语法,而等到1秒之后,才会解析do_stuff,如果不多做一次转义,就有可能构造成一次注入,比如user_string 中插入一个JavaScript编码的构造,截断前边函数,然后构造自己的攻击部分。

这种编码模式,看起来比较绕,但实际上也是JavaScript常出现问题的地方。

安全工程


本来是想着一天写一篇的速度,刚刚好,今天看了CSS 部分,觉得内容很短,而且很多东西配合着 JavaScript看,会有一些收获,于是就把两章放在一起看了,这是个深坑,来自CSS 的问题尚少,都有迹可循,但是来自JavaScript 这个神奇的脚本语言的种种行为模式,思考起来就花费了一些时间,虽然之前JavaScript 有一些基础,但是作者短短几句指出来漏洞,倒是要花费我很多时间去思考这些漏洞到底是怎么构造起来的。

JavaScript 这门语言发展的太快了,而且Web 也发展的太快了,注定这是一个充满了危险的地带,再加上网络这个只用很短时间就从蛮荒时代走向了琳琅满目的东西,总是充满了各种残缺待补的东西,实际上,看如今的网络,似乎没有哪家的服务器没被脱过库了,被爆出来的也都是那些转过几手,拥有的人太多的库了。

由于Web 的漏洞可以来自语言,可以来自浏览器来自客户端,可以来自服务器,来自Web 程序,甚至是程序中一个小小的插件,这两天爆出来的imageTragick 漏洞,不过就是一个图像处理工具,一个小小的漏洞直接让黑客切入到心脏中。

而接下来的部分,也正是将流动在网络上的这些非HTML 文档,以及他们存在的风险,最后还有一个浏览器的插件。书写完成之后,将转入安全特性的探讨上,再次总结书写的过程,应该会有更多的收获。

script>