《《移动代码安全》PPT课件》由会员分享,可在线阅读,更多相关《《移动代码安全》PPT课件(100页珍藏版)》请在金锄头文库上搜索。
1、第第1313章章 移动代码安全移动代码安全 第第13章章 移动代码安全移动代码安全 13.1 引言引言 13.2 移动代码安全技术移动代码安全技术 13.3 Java安全安全 第第1313章章 移动代码安全移动代码安全 13.1 引引 言言 移动代码又称移动代理、可下载代码、可执行内容、远程代码等等,它是指在本地执行的远程代码。传统系统中,执行的代码都是驻留在执行代码的主机上,而对于移动代码,执行的代码则是来自远程主机。 Carzaniga等人1将移动代码进行了分类并与传统的基于客户机服务器技术的分布式系统作了比较:第第1313章章 移动代码安全移动代码安全 (1) 客户机服务器模型:客户机和
2、服务器位于不同的主机上,由客户机向服务器发送请求;处理请求所需的资源和代码都位于服务器端。 (2) 即时响应代码(Code-on-Demand):客户机拥有完成某项操作的资源,但没有如何完成这项操作的代码,客户机通过向远程服务器发送请求,由服务器将代码发送给客户机。这种类型的移动代码包括:Java应用程序、ActiveX控件、MIMI Tcl扩展和Postscript等。第第1313章章 移动代码安全移动代码安全 (3) 远程计算:客户机拥有描述某项服务的代码,而执行代码所需的资源位于远程服务器上。客户机把代码发送到服务器上,由服务器执行代码并把结果回送到客户机。 (4) 移动代理:客户机进程
3、在执行期间为了完成某项服务需要访问一些资源,而这些资源位于远程服务器上,于是客户机把客户机进程移植到服务器端并由服务器完成服务。客户机进程会一直停留在服务器上直到下一个客户机进程移植过来。第第1313章章 移动代码安全移动代码安全 Java应用程序(Applet)和代理(Agent)是移动代码中的两种主要形式。Java应用程序是用户把代码从远程主机下载到本地执行。这种技术在Web浏览器中嵌入了Java虚拟机。这样,Web发布者就可以向用户提供动画、游戏等动态内容,而不仅仅提供静态的HTML页面或依赖于带宽的CGI交互式内容。而代理则相反,用户是把代码发送到网络上以完成用户所需的某项任务。最早的
4、移动代理系统之一是Telescript。第第1313章章 移动代码安全移动代码安全 从上面的讨论可以看出,移动代码是由一方生成,但在另一方控制的环境中运行的代码。这就会带来安全问题。如对于Java应用程序,用户在执行它来实现某些操作的同时要承担恶意代码破坏系统的风险,而代理则要保护其代码免受恶意主机的利用。第第1313章章 移动代码安全移动代码安全 13.2 移动代码安全技术移动代码安全技术 移动代码会导致安全问题的本质在于在运行代码时需要访问系统资源,而代码却是来自于另一台,甚至是不可信的主机。即代码提供者和代码运行者之间可能是互不信任的。移动代码易受到以下几种类型的攻击2: (1) 泄漏用
5、户或主机的机密信息; (2) 拒绝服务:使合法用户无法获得资源;第第1313章章 移动代码安全移动代码安全 (3) 破坏或修改数据; (4) 恶作剧攻击:如在用户屏幕上显示图片或在用户主机上播放音乐等。第第1313章章 移动代码安全移动代码安全 移动代码安全需要考虑以下几个方面:(1) 访问控制:规定谁可以使用代码;(2) 用户认证:识别合法用户;(3) 数据完整性:保证代码在传输过程中未被修改过;(4) 不可抵赖:发送者和接收方不能否认使用过代码;(5) 数据机密性:保护敏感数据;(6) 审计:跟踪移动代码的使用。第第1313章章 移动代码安全移动代码安全 在前面提到,移动代码安全可以从两个
6、方面考虑:一是恶意代码对本地系统的破坏;二是远程恶意主机对代理的非法使用。接下来,我们将从恶意代码和恶意主机两个方面来讨论移动代码的安全技术3。第第1313章章 移动代码安全移动代码安全 13.2.1 恶意代码 对于Java应用程序类的移动代码,一般的操作是用户下载可执行格式的二进制Applet然后运行。这就很容易带来安全问题:用户必须允许不可信的代码在本机运行,而这些代码可能会随机写内存,从而导致系统崩溃;代码甚至可以读、修改以至删除用户个人文件。 在运行Applet之前进行认证可以解决这个问题。通过认证,用户就可以确定它所运行的代码来自于特定的可信源。但是这种方法带来了两个问题:第第131
7、3章章 移动代码安全移动代码安全 (1) 严重限制了用户可以运行的Applet(必须来自可信源),而不可信服务器也可提供有用的、好的代码; (2) 更重要的是来自可信源的代码可能存在bug,从而对用户系统带来恶劣的影响。 对这个问题的理想解决方法就是阻止不安全的行为。接下来讨论三种解决恶意代码问题的技术:安全解释器、故障隔离和代码验证。第第1313章章 移动代码安全移动代码安全 1安全解释器 直接运行二进制代码是很危险的,解决这个问题的常用方法是不使用编译好的可执行代码,而采用解释移动代码的方法。在这种情况下,解释器能很好地控制Applet并能检查每一条指令和每一个状态以决定是否执行Apple
8、t。这样,系统的安全性就在于实现解释器的安全策略的正确性上。安全解释器包括Safe-Tcl、Safe-Tcl扩展、Java等等。第第1313章章 移动代码安全移动代码安全 2故障隔离 采用安全的解释器系统能很好地解决恶意代码的问题。但是,相对编译的机器码而言,解释器存在严重的性能缺陷:执行Java Applet比执行一般的二进制代码要慢得多。为此可以转而采用一种称为“沙盒”(sandbox)的方法来获得安全。第第1313章章 移动代码安全移动代码安全 在沙盒模式中,下载的不可信代码的操作将严格局限于沙盒中。沙盒是由运行代码的主机特别为移动代码分配的地址空间,如Web浏览器特别为Java App
9、let分配的区域。这时,移动代码可以在沙盒中运行,但不会超出沙盒的界限。例如,Applet不能读取或修改存储在用户系统上的文件。在某种情况下,即使用户偶然引入了一个敌意的Applet,这个Applet也不能破坏用户的系统。因此这种沙盒模式为移动代码提供了一个受限的运行环境,在此环境中可以运行从开放网络中获得的不被完全信任的代码(如Applet等)。第第1313章章 移动代码安全移动代码安全 实现沙盒有两种方法: (1) 插入对地址进行条件检查的操作,如果地址非法则产生异常; (2) 简单覆盖对应沙盒地址的高比特位。 第一种方法更适合调试,而第二种方法系统开销小一些。 采用沙盒模式的主要缺点是可
10、下载的代码不再是与平台无关的,而与操作平台无关原本是Java系统的主要设计目标之一。第第1313章章 移动代码安全移动代码安全 3代码验证 第三种技术是一种称为证明携带码PCC(Proof-Carrying Code)的技术4。采用这种技术时,移动代码主机为Applet确定安全策略,然后以Edinburgh逻辑结构(Logical Framework:用来发布安全策略和对证明进行编码)对安全策略编码并发布这个策略。Java Applet作者的任务不仅是把Applet编译成机器代码,还要产生一个安全证明(Safety Proof),用来证明这个代码符合安全策略中指定的安全规则(Safety Ru
11、les)。 第第1313章章 移动代码安全移动代码安全 当用户下载代码后,它只要验证代码中的证明,看是否合法并满足安全规则就行。如果是则加载代码并运行它。 这种方法是否有效的关键在于哪些程序特性可以用LF表示和证实。PCC已成功应用于最小和最大CPU周期限制、内存的安全使用、网络带宽消耗以及类型安全中。此外,为C语言的安全子集开发了PCC编译器,它可以自动生成安全证明。第第1313章章 移动代码安全移动代码安全 第第1313章章 移动代码安全移动代码安全 13.2.2 恶意主机 在讨论了恶意代码问题之后,我们开始讨论恶意主机问题。在移动代理编程中,用户所关心的是其代理能否被正确执行。如一个购物
12、代理可能会携带电子现金,一个主机就可能会欺骗代理使它为某些商品支付高价钱,甚至窃取代理内的金钱。这都是用户所要面对的安全问题。 主机为了执行代理,需要访问代理代码和状态,那么如何保证敏感数据的机密性,或者说如何保证代理算法被忠实地执行呢?Chess13等人认为保护移动代理的限制有:第第1313章章 移动代码安全移动代码安全 (1) 要对代理代码或状态的任意部分保密,就必须采取加密; (2) 我们无法阻止拒绝服务攻击,因为攻击者不需要专门可信硬件的帮助就可以任意修改代理代码或终止代理。 由此可见,要解决恶意主机问题就要从以下两个方面着手: (1) 能够检验篡改; (2) 能阻止机密信息泄漏。第第
13、1313章章 移动代码安全移动代码安全 1检测篡改 我们无法采用技术途径来使代理不受破坏,但是如果能明确地识别出恶意主机,那么法律的、社会的威胁就可能阻止恶意主机的操作员破坏代理。识别恶意主机还可能使代理的所有者因代理的损失而获得某种程度的补偿。 下面将介绍检测恶意主机的技术,这些技术都是基于公开密钥基础设施的,它们允许用户、主机和代理之间相互认证。这些技术主要是要证明一个主机确实是恶意主机,因此,采用数字签名非常重要。第第1313章章 移动代码安全移动代码安全 (1) 执行跟踪。可以通过产生代理程序执行迹的方法来检测篡改。首先,将代理代码的指令分成两类:只依赖于代理内部状态的指令以及其结果依
14、赖于和计算环境交互的指令。对于前一类指令,服务器只有在代理的任一变量的值发生变化时,才在执行迹中记录其新值;对于后一类指令,不但要记录这些新值,还需要对这些值进行数字签名。第第1313章章 移动代码安全移动代码安全 一旦执行完毕,服务器就计算整个执行迹的密码散列值并将它返回给代理的所有者。这时,如果代理所有者怀疑在执行代理时有违规操作,它就可以要求出示执行迹,那么主机执行代理时就必须生成执行迹(执行迹的散列值可被验证),然后检验执行迹就可确定: 主机是否错误地执行了只依赖于内部状态的指令; 主机执行代理过程中,在与计算环境交互时是否欺骗了代理。第第1313章章 移动代码安全移动代码安全 这种方
15、法在实际的操作中存在两个问题: (a) 这种方法在主机违规操作时不会向代理所有者发出警报,而只有在所有者怀疑时提供一种可验证的识别方法; (b) 在需要生成执行迹时,服务器需要保存所有的执行迹,这就给服务器带来了非常高的负担。 第第1313章章 移动代码安全移动代码安全 (2) 认证部分结果。Yee4提出了两种检测恶意主机篡改代理的方法。第一种方法是采用部分结果认证码(PRAC),发送代理的同时发送一组密钥k1、k2、kn。在第i个服务器,代理使用密钥ki对其执行结果进行签名并由此产生PARC;在转移到下一个服务器之前,代理把ki从状态中删除。这样,一个恶意主机就不能伪造来自前一个服务器的部分
16、结果,最糟糕的情况也仅仅是把这些部分结果从代理中移除。第第1313章章 移动代码安全移动代码安全 PRACs允许代理所有者(同时拥有k1、k2、kn)自动地对返回代理中包含的每一部分结果进行密码验证。这些消息可以保证非常好的前向完整性:设移动代理访问一系列的服务器S=s1、s2、sn,而第一个恶意服务器为sc,则对于ic,服务器si的部分结果不是伪造的。 当服务器仅仅通过与正在运行的代理进行欺诈性的交互实现篡改时,上述方案就无法自动检测到。此外,只有在代理所有者怀疑主机时才会产生PRAC所有的PRAC都是密码有效的,但有些可能在语义上无效。第第1313章章 移动代码安全移动代码安全 Yee提出
17、了一种投机的方法来检测语义上的篡改。对于程序x,设y为x的执行迹。主机执行完代理后把y发送给代理所有者,由所有者验证y。由于执行迹可能会很大,因此传输它的带宽开销会非常大。从而,主机把y编码成全息样片y。样片y具有如下特性:代理所有者只需要检查y某些位就能确信其正确与否。这样,服务器就可以采用哈希树的方案将样片散列成一个小的根值,然后把根值返回给代理所有者,由所有者判断y是否正确。这种方法的主要缺点是服务器的负荷问题。构造全息样片y是一个全NP问题(不存在多项式时间算法解的一类问题,全NP问题则是最为困难的一类),在执行迹y大到很难传输到代理所有者时,构造y是不能实现的。第第1313章章 移动
18、代码安全移动代码安全 2保密 在有些情况下,事后检测是不适当的或是不能解决问题的。如有时候采用法律行动的代价比篡改带来的经济损失更大;有些时候,代理发送数字签名,但由于某些原因其私钥已被泄漏。为此,Sander和Tschudin13提出了一种理论上的方案以允许代理对恶意主机保留一些机密。 方案的本质是这样的:代理程序计算某个函数f,主机为代理计算f(x)但不需要知道f的任何实质性的内容。协议描述如下:第第1313章章 移动代码安全移动代码安全 (1) 代理所有者加密f; (2) 所有者创建程序P(E(f)来实现E(f),并把它放在代理中; (3) 代理到达远程主机,在远程主机上计算P(E(f)
19、(x),并把值返回给所有者; (4) 所有者解密P(E(f)(x)并获得f(x)。 其中,E为某个加密函数。协议的基本思想是把基本算法转化成杂乱算法,使其结果只对代理所有者有意义。第第1313章章 移动代码安全移动代码安全 3总结 相比恶意代码而言,恶意主机问题更难以处理,目前还没有实际的、在计算上可行的方法用来检测篡改,而一些可以用来证明发生了篡改的技术也会大大增加服务器的负荷。此外,在一个不友好的环境中运行代理时,有没有可能为代理提供某种类型的机密性还是一个未知数。这些问题的存在或许可以解释移动代理为什么得不到广泛应用。第第1313章章 移动代码安全移动代码安全 13.3 Java 安安
20、全全 13.3.1 Java综述 Java语言是Sun Microsystems公司开发的。在Sun Microsystems公司提供的技术报告5中称:“Java是一种简单的、面向对象的、适合网络编程的、解释的、健壮的、安全的、与结构无关的、可移植的、高性能的、多线程的动态语言。”第第1313章章 移动代码安全移动代码安全 简单:Java是一种易于使用的编程语言,其简单性体现在以下几个方面: (1) 它和C+类似(C+程序员可以很快掌握Java编程技术); (2) 它摒弃了C+中许多很少用、很难理解和容易混淆的特性,如操作符重载等; 第第1313章章 移动代码安全移动代码安全 (3) Java
21、实现了自动垃圾收集,从而简化了Java编程。在C和C+中,一个复杂的操作是内存管理:内存分配和内存释放。通过自动垃圾收集(周期性的释放没有被引用的内存)不但可以使编程简单化,还可大大地减少程序中的bug; (4) Java程序很小,易于通过网络下载。第第1313章章 移动代码安全移动代码安全 面向对象:和C+一样,Java是一种面向对象的编程语言。 适合网络编程:Java拥有广泛的、能轻易处理TCP/IP协议的运行库,因此相比C和C+而言,Java更容易创建网络连接。Java应用程序通过URL打开和访问网络上的对象与编程人员访问本地文件系统一样简单。 健壮性:Java能检查程序在编译和运行时的
22、错误。类型检查可以检查出许多开发早期出现的错误;Java没有采用C+中的指针算法而实现了真数组,从而避免了覆盖内存和破坏数据的可能。第第1313章章 移动代码安全移动代码安全 安全:设计Java是为了用于分布式网络环境,因此安全在这里就显得非常重要。本节的后续内容将详细介绍Java的安全实现以及其潜在的安全威胁。 与结构无关:Java程序是在网络上传播和应用的,而网络上的不同主机的CPU、操作系统结构等等都不尽相同,为了使Java应用程序能在网络上的任何一台主机上运行,编译器必须产生一个与结构无关的文件格式只要处理器上有Java虚拟机系统,此文件格式的代码就可以在此处理器上运行。Java的做法
23、是让编译器生成与特定计算机体系结构无关的字节码指令,这些字节码指令在任何机器上都很容易解释,并能很容易地翻译成本机机器代码。第第1313章章 移动代码安全移动代码安全 可移植:与体系结构无关的特性使得Java应用程序可以在配备了Java解释器和运行环境的任何计算机系统上运行,这为Java应用程序便于移植打下了良好基础。但仅仅如此还不够,不同操作系统基本数据类型的设计实现是不同的,如在中整数(int)为16位,在Windows 95中为32位,而在DEC Alpha中却为64位。这不利于代码的移植。为此,Java定义了独立于平台的基本数据类型及其运算,从而使得Java数据在任何硬件平台上都是一致
24、的。其次,Java编译器本身是用Java语言编写的,而运算系统是用ANSI C语言写的。总之,在Java语言规范中没有任何与具体实现相关的内容。第第1313章章 移动代码安全移动代码安全 解释:Java是一种解释性语言,其解释器可以将Java字节码翻译成本地机器指令并运行,而不需要存储字节码。 高性能:由于解释字节码的性能一般都是比较高的,从而Java可以在运行时直接将字节码翻译成机器指令。Sun Microsystems的SPARCStation 10在翻译代码时,可以在每秒钟内调用300 000个方法,这和直接生成机器目标代码C/C+的性能不相上下。第第1313章章 移动代码安全移动代码安
25、全 多线程:Java提供了一个内建的机制来提供对多个并发子任务的支持。 动态:在C+程序设计过程中,每当在类中增加一个实例变量或一种成员函数后,引用该类的所有子类都必须重新编译,否则将导致程序崩溃,而Java是一种动态语言,它不是把所有的类静态地编译成机器码,而是由程序动态地装入运行过程中所需要的类。第第1313章章 移动代码安全移动代码安全 13.3.2 Java底层安全性实现 1. 底层安全 Java解释器通过以下几种方式实现底层安全: 1) 通过发布源代码获得安全 如果需要,Java解释器和编译器均可获得完整的源代码。对Java源代码可以执行安全审计。第第1313章章 移动代码安全移动代
26、码安全 2) 通过明确定义获得安全 Java语言的定义非常严格: 保证所有的基本类型使用指定的长度; 所有的操作必须按指定的顺序执行。 明确的定义可以保证两个(正确的)Java编译器执行同一个程序不会得到两个不同的结果。第第1313章章 移动代码安全移动代码安全 3) 通过摒弃指针获得安全 Java摒弃了C语言中的指针算法,因此编程人员无法伪造指针来访问内存。对类文件中所有方法和实例变量的引用都是通过符号名来实现的,这就可以避免在C语言中利用指针非法访问内存而提升权限一类的攻击,如缓冲区溢出等。 第第1313章章 移动代码安全移动代码安全 4) 通过垃圾收集获得安全 在C/C+中,编程人员经常
27、面对的问题是内存分配和释放。当释放内存不当,如没有释放不再使用的内存或两次释放同一内存区会导致安全问题。Java通过自动垃圾收集(周期性地释放没有被引用的内存)来避免这些问题。第第1313章章 移动代码安全移动代码安全 5) 通过在编译时的严格检查获得安全 Java编译器在编译时要进行详尽的、严格的检查以尽可能地检测编程中的错误。Java语言是强类型的: 在运行期,如果不进行明确的检查不能把对象分配给某个子类; 要检查所有对方法和变量的引用以确保对象具有合适的类型。 整数不能转换为对象,对象也不能转换为整数。 编译器还要确保程序没有访问未初始化的本地变量。第第1313章章 移动代码安全移动代码
28、安全 2. 类文件验证 虽然编译器可对类型进行详尽的检查,但攻击者仍然可能通过使用专门的编译器实现攻击。如HotJava浏览器是下载已经编译好的类文件,它无法确定下载的字节码是由可信的Java编译器编译的还是由某个有恶意企图的编译器编译的。 第第1313章章 移动代码安全移动代码安全 在编译时实现对类型的检查还存在版本不一致的问题。如用户编译好了一个类,假设 PurchaseStockOptions是TradingClass的一个子类。但是在类编译完后,TradingClass的定义可能会发生变化:某个方法不用了或方法的参数改变了;变量类型变了。而且,方法或变量也可能从公有变成私有。 为此,所
29、有外来的类文件都需要经过一个验证器,由验证器确保类文件具有正确的格式。第第1313章章 移动代码安全移动代码安全 字节码验证器同样可以增强解释器的性能。利用字节码验证,解释器就不用对每一条解释的指令进行检查,而会认为这些检查都已经在此之前完成了。如解释器能肯定代码遵守以下限制: 代码中不存在缓冲区溢出; 所有的寄存器访问和存储都是合法的; 所有字节码的参数都是正确的; 不存在非法的数据转换。第第1313章章 移动代码安全移动代码安全 验证器独立于Java编译器,它使用户可以放心的从防火墙之外下载Java代码。 下面详细介绍验证器的验证过程,其中提到的类文件格式的具体细节可参考参考资料5: (1
30、) 验证的第一步发生在将类读入解释器时。这一步要确保类文件具有类文件的格式:开始的几个字节必须包含正确的魔幻数;所有可验证的属性都具有正确的长度;类文件的末尾不能被截断,也不能添加额外的字节;常数存储库不能包含不可识别的信息。 第第1313章章 移动代码安全移动代码安全 (2) 第二步更进一步验证类文件的格式: 确保final类没有被继承,final方法没有被覆盖; 每个类必须有一个超类; 确保常数存储库满足一定的限制,如其中的类引用必须包含一个指向存储库中的一个unicode字符串引用的域。 常数存储库中所有的域引用和方法引用都必须有合法的名字、合法的类和合法的类型签名。第第1313章章 移
31、动代码安全移动代码安全 (3) 这一步是类验证中最复杂的一步,在这一步中需要验证每一个方法的字节码。这一步要保证: 堆栈的大小和它包含的对象类型保持不变; 除非已知包含了一个适当类型的值,否则不能访问寄存器; 以适当的参数调用方法; 以适当类型的值修改域; 所有的操作码在堆栈和寄存器中具有适当类型的参数。在“字节码验证”中我们将进一步描述这一步的细节。第第1313章章 移动代码安全移动代码安全 (4) 在第三步中,除非有必要,否则不会装载类文件。如一个方法调用另一个返回foobarType类型对象的方法时,如果立即把同一类型的域分配给返回的对象,验证器就不会验证foobarType类型是否存在
32、;如果把anotherType类型的域分配给返回的对象,则必须装载foobarType和 anotherType的 定 义 以 确 保 foobarType是anotherType的一个子类。 引用类的指令第一次执行时,验证器需:第第1313章章 移动代码安全移动代码安全 如果还没有装载这个类则装载; 验证当前正在执行的类是否被允许引用给定的类。 指令第一次调用方法或访问、修改域时,验证器需: 确保方法或域存在于给定的类中; 检查方法或域是否具有要求的签名; 检查当前正在执行的方法是否有权访问给定的方法或域。 第第1313章章 移动代码安全移动代码安全 这一步中,验证器不需要检查堆栈中对象的类
33、型,因为在第三步中已经执行了这项检查。 在执行完验证后,字节码流中的指令就会被另一种形式的指令所代替。如操作码new被new_quick代替。这个替换的指令表示已经执行了对这个指令的验证,不需要再次验证了。第第1313章章 移动代码安全移动代码安全 3字节码验证 类文件验证的第三步是字节码验证,这是类文件验证过程中最复杂的一步。首先,把组成虚指令的字节分成一系列的指令,每一个指令开始位置的偏移量保存在一个位表中。然后,验证器再次扫描这些字节并解析指令。这一步中把每个指令转换成一个结构。检查每个指令的参数(如果有的话)以确保它们是合理的: 所有的流程控制指令必须到达一个指令的开始,不允许有到达指
34、令内部的分支。类似的,不允许出现到达代码开始前或代码结束后的分支。第第1313章章 移动代码安全移动代码安全 所有的寄存器引用必须是引用合法的寄存器。除方法指明的所能用的寄存器外,代码不能访问和修改任何其它的寄存器。 所有对常数存储库的引用必须是引用适当类型的条目。如操作码ldc1只能用于整数、浮点数或string类型,而操作码getfield必须引用一个域。 代码不能在某个指令中间结束。 对每一个异常处理程序,其起始点和结束点都必须指向一个指令的开始。异常处理器的偏移必须是合法的指令;起始点必须在结束点之前。第第1313章章 移动代码安全移动代码安全 对每一条指令,在执行之前,验证器要跟踪堆
35、栈和寄存器的内容。对于堆栈,需要知道堆栈的长度和堆栈中元素的类型;对于寄存器,需要知道寄存器内容的类型或寄存器内的值是合法的。在确定堆栈中值的类型时,字节码验证器不需要区分不同的整数类型(如byte、short和char)。 接下来初始化数据流分析器。对于第一条指令,编号较小的寄存器包含方法类型签名所指示的类型;堆栈为空;所有其它的寄存器包含非法值;对于其它指令,指示该指令没有被访问,还没有其堆栈或寄存器的信息。第第1313章章 移动代码安全移动代码安全 最后,运行数据流分析器。对每一条指令都有一个“changed”位来表示这个指令是否需要查看。最初只有第一条指令设置了“changed”位。数
36、据流分析器执行如下循环: (1) 找到一个设置了“changed”位的虚拟机器指令,如果没有找到,则表示该方法已被验证。清“changed”位。 (2) 仿真指令对堆栈和寄存器的影响: 如果指令要使用来自堆栈的值,需确保堆栈中有足够的元素,并且堆栈顶端元素具有适当的类型,否则,验证失败。第第1313章章 移动代码安全移动代码安全 如果指令使用了寄存器,需确保指定的寄存器包含了适当类型的值,否则,验证失败。 如果指令要把值压入堆栈,往堆栈顶端加入指示的类型,需确保堆栈有足够的空间存放新元素。 如果指令需要修改寄存器,需说明寄存器当前包含了新的类型。第第1313章章 移动代码安全移动代码安全 (3
37、) 确定跟随在当前指令后面的虚拟机器指令。后续指令可能是: 如果当前指令不是无条件goto、return或throw,则为下一条指令;如果可以离开最后一条指令,则这一步失败。 有条件或无条件转移的目标。 当前指令所有的异常处理程序。第第1313章章 移动代码安全移动代码安全 (4) 在当前指令的末尾,把堆栈和寄存器的状态合并到下一条指令中。在异常处理的情况下,需要更改堆栈使得堆栈包含一个单一的对象,其异常类型由异常处理器信息指明。 如果下一条指令是第一次访问,则在执行下一条指令之前指明:由第(2)步和第(3)步计算得到的堆栈和寄存器的值是堆栈和寄存器的状态;设置下一条指令的“changed”位
38、。 如果指令以前访问过,则把第(2)步和第(3)步计算得到的堆栈和寄存器的值合并到已有的值中;如果有改动则设置“changed”位。第第1313章章 移动代码安全移动代码安全 (5) 回到第(1)步。 字节流分析器对某些指令和数据类型,如长整数、构建函数、异常处理程序、Try/Finally等的具体验证过程可参考参考资料6。第第1313章章 移动代码安全移动代码安全 13.3.3 Java的沙盒模型 传统的操作系统允许应用程序对机器有完全的访问,因此不能信任运行环境。为此,安全策略一般要求在运行一个程序之前,需要在某种程度上信任程序。如在运行从Web上下载的程序之前,安全策略要求对程序进行病毒
39、检查以及检查源代码以发现恶意代码。这种方法存在两个问题:第第1313章章 移动代码安全移动代码安全 (1) 建立对应用程序信任的检查在实际操作中非常复杂,且非常费时间。一般人们不会花时间去研读源代码以发现隐藏在其中的恶意行为。 (2) 要使病毒检查有效,需要及时地维护:病毒库要及时更新、扫描程序要安装在每一台计算机上等等。第第1313章章 移动代码安全移动代码安全 Java采用了一种新的方法:沙盒。Java把Applet的所有操作都严格限制在一个称之为“沙盒”(一个由Web浏览器专门为这个Applet分配的地址空间)的区域内。Applet在其沙盒内可以做任何事,但超出此边界就不能有任何操作。沙
40、盒模型实现了在一个信任环境中运行不信任的代码的功能。这样,即使用户运行了一个恶意的Applet,也不会给用户带来什么损失。沙盒由几个不同的系统操作组成,下面将逐一介绍。第第1313章章 移动代码安全移动代码安全 1类装载程序ClassLoader Java运行时有两种不同的方式来装载一个新的类。其默认机制是从本地机器上的文件中装载一个类,这种机制不需要类装载程序ClassLoader。另一种方式是通过网络等装载一个类,这时需要一个相关的类装载程序ClassLoader,由ClassLoader负责将类的原始数据(如网络上传输的字节)转化成表示那个类的内部数据结构。第第1313章章 移动代码安全
41、移动代码安全 类装载程序除了要完成从网络上获得一个Applet的可执行代码外,还实施了命名空间的体系结构。一个命名空间规定了一个Applet能访问JVM(Java虚拟机)的其它哪些部分。通过为本地磁盘上的可信任代码维护一个单独的命名空间,类装载程序可以防止不可信的Applet获得对系统可信部分(往往需要更多特权才能访问)的访问权。 从网络上下载的Applet不能创建其自己的类装载程序,也不能调用系统类装载程序中的方法。第第1313章章 移动代码安全移动代码安全 2验证器 在运行一个新下载的Applet之前,类装载程序要调用验证器进行类文件验证。 3安全管理器 安全管理器增强沙盒的边界。在App
42、let试图执行可能导致本地机器崩溃或访问信息的操作时,JVM首先向安全管理器询问此操作是否可以安全地执行。只有安全管理器准许了这项操作,虚拟机才能执行,否则,虚拟机将产生一个安全异常并向Java控制台写出错信息。第第1313章章 移动代码安全移动代码安全 下面列举了安全管理器禁止不可信Applet执行的部分操作: 对本地文件的读写; 删除文件; 执行操作系统命令或本地代码; 载入一个直接调用本地方法的新的动态库; 与不是此Applet源主机的机器建立连接; 建立一个新的进程。第第1313章章 移动代码安全移动代码安全 一个应用程序或Web浏览器只能有一个安全管理器,这可保证所有的访问检查都是由
43、一个执行单一安全策略的安全管理器来完成的。安全管理器在启动时载入,它不能被扩展、重载或替代。显然,Applet不能创建其自身的安全管理器。 4语言特性 在Java底层安全性实现一节中,我们可以看出,Java具有许多可以保护安全系统完整性,并防止某些常见攻击的特性。 第第1313章章 移动代码安全移动代码安全 13.3.4 扩展Java安全 Java沙盒模型可以保护终端用户机器和网络计算资源不受恶意Applet的破坏和进行信息窃取,从而用户可以运行来自网络上的不可信代码而不会有安全风险。但是沙盒模型没有涉及其它的一些安全和保密性问题: 认证可以帮助确认一个Applet确实来自其声明的主机; 数字
44、签名和认证过的Applet可以提升为可信Applet,从而可以在较少的安全限制前提下运行; 加密可以保证Applet客户端和Internet上服务器之间传输数据的机密性。 第第1313章章 移动代码安全移动代码安全 1签署JAR文件 所有的网络化系统都容易受到潜在的中间人攻击。在这种攻击中,客户端与网络上的合法服务器进行连接并请求某种操作;而攻击者,即这里所说的中间人窃听到这些请求后就等待服务器的响应,然后截获响应并向客户端发送一个伪造的应答;这时客户端就会根据这些伪造的信息进行某种操作,或者运行攻击者提供的程序而使攻击者获得对机器的访问权。如攻击者可观察一个基于Internet的银行站点;当
45、客户端访问其提供付款服务的页面时,攻击者就可以截获银行的响应,而把一个可以模仿银行服务并可窃取用户信用卡副本和银行账号的恶意Applet作为响应发送给客户。 第第1313章章 移动代码安全移动代码安全 通过对Applet使用“数字签名”可以阻止这种攻击。首先,把所有Java代码和相关文件捆绑成一个Java档案(JAR);然后根据JAR的内容利用数字签名算法产生一个用字符串表示的数字签名。通过验证其数字签名就可以确定这个JAR的来源,从而有效地防止了中间人攻击。 JAR文件从某种程度上还可帮助解决另外一个问题。目前,许多Java Applet需要很长时间才能下载下来。这和当前的Internet协
46、议有关。 第第1313章章 移动代码安全移动代码安全 目前的Internet协议每次只请求和传送一个文件,每请求一个文件需要一定的开销,而一个页面和Java Applet一般都由许多小文件组成,从而可能使得请求文件和等待响应的时间比真正传输信息的时间还要长。采用JAR文件后,把Applet和Web页所需要的所有信息捆绑成一个文件,从而请求整个页面就只需要一个请求。对于大部分的页面,这可大大减少下载时间。第第1313章章 移动代码安全移动代码安全 2灵活的策略 数字签名可以赋予Java Applet某种可信度,从而可以放宽对某些Applet的Java安全限制。如上面提到的家庭银行Applet,如
47、果采用了数字签名,它就可以在用户硬盘上建立自己目录以存储账号、信用卡号、口令、个人身份号码PIN和其它经常要用的信息,这时的终端用户不必经常性地重输这些信息。第第1313章章 移动代码安全移动代码安全 数字签名过的Applet可以创建自己的环境。如果终端用户已提前指示Java系统某个特定的Web发布者是可信的,而且某个来自此Web发布者并签名了的Applet被验证通过,则Java安全管理器就可允许这个Applet的操作超出其沙盒的范围,也就是说,把这个Applet看成一个普通的应用程序。第第1313章章 移动代码安全移动代码安全 安全管理器还可以根据对特定Web发布者的信任程度或对整个Inte
48、rnet的信任程度而执行不同的控制策略。如一个安全意识很强的用户可能将系统配置成签名了的Applet只能在沙盒范围内执行;未签名的Applet根本就不允许执行。另外一个用户可能将系统配置成银行Applet只能访问硬盘上的某个特定目录,而一个网络游戏Applet可以访问另外一个目录,同时所有其它的Applet只能在沙盒的范围内执行。第第1313章章 移动代码安全移动代码安全 3审计 审计是另一个重要的安全要素。审计软件将维护系统上发生的每一件事的记录。当出现错误时(无论是偶然还是由于bug,或者是由于攻击造成的),系统管理员和安全人员就可以根据审计迹推测出所发生的事,从而可以帮助他们确定如何防止
49、错误的再次发生。虽然审计不能阻止事故和攻击,但在错误发生后,它是把事情弄清楚的重要工具。 目前版本的Java审计功能很有限,还没有管理员可以依赖的审计能力,而且其记录的特征很不详细。第第1313章章 移动代码安全移动代码安全 4加密 虽然沙盒模型和对Applet的数字签名可以阻止恶意Applet的破坏和中间人攻击,但在Internet上,Applet和服务器之间的信息传输仍然易遭到窃听。这是因为Internet本身就是一个不安全的传输煤质。攻击者在Internet的关键点可以获得经过该关键点的所有的信息。由此,攻击者可以侦听出入一个银行的所有流量,也可以只获取通过的信用卡号和其它一些信息。为了
50、防止这种攻击,我们需要对Applet和服务器之间的所有流量进行加密,从而使它不可读。第第1313章章 移动代码安全移动代码安全 13.3.5 Java安全开发建议 虽然Sun声称Java是一种安全语言,而且Java也采用了一些安全结构以及内建了一些安全特性,但是Java仍无法避免安全问题,其应用程序中仍存在大量的安全漏洞和隐患。为了使用Java开发安全的程序,可参考以下建议101112。第第1313章章 移动代码安全移动代码安全 1) 限制对类、方法和变量的访问 把类、方法和变量声明为公有会给攻击者提供潜在的入口。为此:(a) 不要使用公共域或变量,把它们声明为私有的,并提供访问函数以限制对它
51、们的访问;(b) 除非有很好的理由,否则把方法都设为私有的(如果确实没这样做,说清楚其理由)。非私有的方法可能会接收受污染的数据,因此必须保护这些方法(除非已经用其它方式对它们进行了保护)。第第1313章章 移动代码安全移动代码安全 2) 避免使用静态域变量 静态域变量是附着在类而非类的实例上,而类可以被其它类所定位,其结果就是可以通过其它类找到静态域变量,这就很难保证它们的安全。 3) 永远不要把可变对象返回给潜在的有恶意代码(因为代码可能会改变它) 注意,数组是可变的(即使数组的内容不可变),所以不要返回一个含有敏感数据的内部数组的引用。第第1313章章 移动代码安全移动代码安全 4) 永
52、远不要直接保存用户给定的可变对象(包括对象的数组) 如果直接保存用户给定的可变对象,用户可以把对象交给安全代码,让安全代码“检查”对象,并在安全代码试图使用数据时改变数据。应该在内部存储数组前复制它们,而且要小心(例如,警惕用户编写的复制例程)。第第1313章章 移动代码安全移动代码安全 5) 不要依赖于初始化 许多Java开发人员认为如果不运行构建器就无法为一个对象分配内存,这是不对的,事实上有许多方法可以为未初始化的对象分配内存。避免这个问题的一个简单方法就是书写自己的类,从而在对象进行某项操作前验证对象。其做法为 使所有的变量为私有。如果要允许外部代码访问一个对象内的变量,可以通过set
53、和get方法来实现(这就使外部代码不能访问未初始化的对象)。第第1313章章 移动代码安全移动代码安全 为每一个对象添加一个新的私有布尔变量initialized。 在返回之前让每一个构建器设置初始化的变量作为最后一个操作。 在进行进一步操作之前,让每一个nonconstructor方法验证initialized为真。第第1313章章 移动代码安全移动代码安全 如果自己书写的类具有静态的初始化程序,则需要在类的层次做同样的操作。也就是说,对于任何一个具有静态初始化程序的类,需要: 使所有的静态变量私有。如果要允许外部代码访问类中的静态变量,可以通过静态set和get方法来实现(这就使外部代码不
54、能访问未初始化的静态变量)。第第1313章章 移动代码安全移动代码安全 为类添加一个新的私有静态布尔变量classInitialized。 在返回之前让静态构建器设置初始化的变量作为最后一步操作。 在进行进一步操作之前,让每一个静态方法和每一个构建器验证classInitialized为真。第第1313章章 移动代码安全移动代码安全 6) 除非有很好的理由,否则使每件事都final(终结) 如果某个类或方法不是final的,攻击者就可以用某种危险且无法预知的方法来扩展它。注意,作为安全性的交换,这会带来可扩展性的丧失。 7) 不要在安全性上依赖包的范围 在同一个包内可以访问没有明确标记为pub
55、lic、private或protected的类、方法和变量。Java类不是关闭的,因此,攻击者可以向包中引入一个新类,并用此新类来访问用户以为保护了的信息。(某些类,如java.lang,缺省是关闭的,而且某些Java虚拟机(JVM)会让用户关闭其它包,但最好假设包不是关闭的。)第第1313章章 移动代码安全移动代码安全 8) 不要使用内部类 有些Java语言的书上称只有封装内部类的类才可以访问被封装的内部类,这是不正确的,因为Java字节码没有内部类的概念,在内部类转换为字节代码时,会被转换为这个包中任意代码可以访问的类。更糟的是,被封装类的私有域会静悄悄地变成非私有的,从而允许内部类访问!
56、第第1313章章 移动代码安全移动代码安全 9) 最小化特权和避免标记代码 运行没有标记的代码不需要任何专门的特权。没有专门特权的代码不会带来什么危害,为此应避免标记代码。但是有时代码需要获得和使用特权以执行某种危险的操作,此时应使代码特权最小化,同时应更仔细地审阅特权代码。 第第1313章章 移动代码安全移动代码安全 10) 如果一定要标记代码,应该把它们都放在一个档案文件里 此规则的目的是防止攻击者使用混合匹配攻击。在混合匹配攻击中,攻击者构建新Applet或库,把某些标记类与有恶意的类连接在一起,或把根本意识不到会被一起使用的标记类连接在一起。通过把一组类标记在一起,就可以使这种攻击更高
57、明。现有的代码标记系统在防止混合匹配攻击上做得还不够,所以这一规则还不能完全防止此类攻击。但使用单个档案没什么坏处。第第1313章章 移动代码安全移动代码安全 11) 使类不可被复制 Java的类复制机制允许攻击者不运行构建函数就实例化某个类。只要在每个类里定义如下方法就可使类不可被复制: public final void clone() throws java.lang.CloneNotSupportedException throw new java.lang.CloneNotSupportedException(); 第第1313章章 移动代码安全移动代码安全 如果确实需要使类可被复制
58、,那么可以采用几个保护措施来防止攻击者重新定义复制方法。如果定义了自己的复制方法,就使它final;如果不是,至少可以通过增加如下内容来防止复制方法被恶意地重载: public final void clone() throws java.lang.CloneNotSupportedException super.clone(); 第第1313章章 移动代码安全移动代码安全 12) 使类不可顺序化 顺序化可以使攻击者看到对象的内部状态,甚至私有部分。攻击者将对象顺序化为可以读的字节数组,这就使得他可以检查对象所有的内部状态,包括标记为私有的域和引用对象的内部状态。要防止这一点,需要在类里增加如
59、下方法实现对象的不可顺序化(定义成final方法是为了使攻击者不能重载被攻击者定义的子类):第第1313章章 移动代码安全移动代码安全 private final void writeObject(ObjectOutputStream out) throws java.io.IOException throw new java.io.IOException(Object cannot be serialized); 在顺序化没问题的情况下,也应该对包含直接处理系统资源的域和包含与地址空间有关信息的域使用临时关键字。否则,解除类的顺序化就会造成不适当的访问(可能还需要把敏感信息标识为临时的)。第
60、第1313章章 移动代码安全移动代码安全 如果对类定义了自己的顺序化方法,就不应该把内部数组传递给需要数组的DataInput/DataOuput方法。其理由在于:所有的DataInput/DataOuput方法都可以被重载。如果某个可顺序化的类向某个DataOutput(write(byte b)方法直接传递了一个私有数组,那么攻击者就可以构建子类ObjectOutputStream并重载write(byte b)方法,从而可以访问并修改那个私有数组。注意,缺省的顺序化并没有把私有字节数组域暴露给 DataInput/ DataOutput字节数组方法。第第1313章章 移动代码安全移动代码
61、安全 13) 使类不可被解顺序化 即使类不可被顺序化,它依然可以被解顺序化。攻击者可以构建一个字节序列,使它碰巧可以解顺序化为某个类实例,而且具有攻击者选定的值。换句话说,解顺序化是一种公共的构建函数,允许攻击者选择对象的状态显然这是一个危险的操作!要防止这一点就需要防止能将一字节流解顺序化为类的一个实例,这可通过声明readObject方法来实现:第第1313章章 移动代码安全移动代码安全 private final void readObject(ObjectInputStream in) throws java.io.IOException throw new java.io.IOExc
62、eption(Class cannot be deserialized); 第第1313章章 移动代码安全移动代码安全 14) 不要通过名称来比较类 在Java中可以使用同一名字表示不同的类,这样,攻击者可以用相同的名称定义类,同时可能会给这些类赋予不恰当的权限,因此,不要使用名字来比较类。下面是一个判断某个对象是否含有某个给定类的错误方式: if (obj.getClass().getName().equals(Foo) 第第1313章章 移动代码安全移动代码安全 如果确实需要判断某个对象是否含有某个给定类名,则需要严格按照规范并确保使用当前名字空间(当前类的ClassLoader所在名称空间)。因此,应该使用如下形式: if (obj.getClass() = this.getClassLoader().loadClass(Foo) 如果要判断两个对象是否含有完全相同的类,最好采用直接比较类对象的方法,即使用如下形式: if (a.getClass() = b.getClass() 第第1313章章 移动代码安全移动代码安全 15) 不要把秘密(密钥、密码或算法)存储在代码或数据里 恶意JVM可以迅速看到这些秘密。打乱代码并不能在高明的攻击者面前真正隐藏代码。