Java安全——语言本身的设计

内在安全机制

Java语言本身的安全机制是要保护内存资源——保证内存完整性,核心的安全特性要确保程序不能非法解析或修改驻留在内存中的机密信息。从语言本身的设计角度考虑,就是要设计一组规则,在所构建的运行环境中,程序对象对内存的操作是经过定义的而不是任意的。

Java的强制约束

  1. 必须严格遵循访问方法的要求。必须依照程序员制定的访问级别进行相关方法的操作。如果不遵守则会产生异常。
  2. 不能访问任意的内存地址。 Java没有指针的概念,因此不会像C++一样拿到一个指针强制转换成内存指针,再利用查找内存的方法得到本不该被获取的信息。
  3. 不能对final实体再做改动。
  4. 变量在初始化前不能使用。如果能读取未初始化的变量,就等同于可以读取任意内存地址。可以通过声明一个大的对象而盗取主机内存中的信息。
  5. 对于所有数组访问进行越界检查。越界检查除了可以减少程序错误意外,另一大贡献就是安全保障。如果整数数组后紧接着存放一个字符数组,那么通过整数数组的越界写,可以改变字符数组的内容。
  6. 对象不能任意强制转换为其他类型的对象。这种类型转换的限制不仅在编译器层面,在JVM里也做了强限制,在绕过编译器的转换(比如把被转换的对象标记为Object),JVM在运行时检查也会抛出ClassCastException

Java语言通过上面几条约束,从语言层面保护了内存,不会允许程序在没有获得正确的访问权限时读取到本不该访问的内存。

序列化怎么保障安全

这个题目不太好,因为序列化无法保证安全。Java允许通过实现java.io.Serializable接口来使内存对象序列化为一组字节码。这组字节码通过网络或者文件等方式被其他地方的代码读取并重建一个相同结构和内容的内存对象。这是Java的序列化和反序列化过程。作为一组字节码存储在磁盘文件或者数据流里,原则上是允许被修改的。那说白了,序列化无法保障安全。但是考虑到Java语言要设计支持一组规则,至于序列化的安全,就交给使用者自己保障。

序列化的安全设计规则:

  1. 可序列化的对象必须实现java.io.Serializable接口,这相当于给用户打预防针,告知其要考虑这个对象的安全问题。
  2. 序列化对象声明transient变量,那么该变量不被序列化。这相当于提供了保护数据的机制。

单单从这两个层面看,已经是Java语言能做到的最大化问题了。再多,则影响到了序列化本身要实现的功能。那么具体怎么做呢?就像我们常规传输数据一样——加密,你可以选择对要序列化的变量和属性加密,在反序列化时解密来增强安全。

安全规则实施

当然这里讲到的实施其实不仅仅针对安全,但是这些实施阶段确实增强了安全性。换个角度,我们其实是看Java程序的运行过程如何对应这些语言设计规则。

编译

编译阶段,可以避免“约束”中提到的前4条规则。数组越界是运行时问题,而类型转换,在编译阶段只能做到无关类型的转换,比如下例:

    static class Foo {
        int x;
    }

    static class Bar {
        int x;
    }

    public static void main(String[] args) {
        Foo foo = new Foo();
        Bar bar = (Bar) foo;
    }

这时编译器会提示“Cannot cast from Foo to Bar”。但是如果稍作修改,将Foo替换为Object,则编译器无能为力。

    static class Foo {
        int x;
    }

    static class Bar {
        int x;
    }

    public static void main(String[] args) {
        Object foo = new Foo();
        Bar bar = (Bar) foo;
    }

链接

我们都知道,编译只是做Class文件,JVM的介入是要从load class开始的。而加载完class文件后的第一件事就是链接。链接包括验证、准备和解析这几个步骤,而验证阶段,就是安全规则介入的一个阶段。验证阶段就引入了JVM的字节码校验器。

这个阶段主要是来防御恶意编译器的攻击,或者是一些无意的程序错误。比如一个类FooBar设计如下:

public class FooBar {

    public String val = "abcd";
}

类FooBarTest引用了这个类并更改了val变量:

public class FooBarTest {

    public static void main(String[] args) {
        FooBar foobar = new FooBar();
        foobar.val = "abcde";
        System.out.println(foobar.val);
    }

}

这时我们编译并运行FooBarTest,会打印abcde。而现在如果去修复FooBar,将public改为private,然后只编译FooBar,则不会发生错误。然而本来这时也编译FooBarTest会导致编译错的。但是因为疏忽导致没有这么做,那么如果没有链接校验,FooBarTest就是错误的执行了。然而因为字节码校验器的存在,运行FooBarTest会抛出

Exception in thread "main" java.lang.IllegalAccessError: tried to access field FooBar.val from class FooBarTest
    at FooBarTest.main(FooBarTest.java:5)

优雅的解决了这个问题。

字节码校验器通过两部分来实现这种校验。首先,其作为一个微型的定理证明机,会证明class满足下列条件(只做检查):

  1. 类文件格式正确;
  2. 不会基于final派生子类,也不会覆盖final方法;
  3. 只有一个父类;
  4. 没有对primitive类型数据进行非法转型(int->Object);
  5. 对象之间没有进行类型转换;
  6. 操作数栈不会出现溢出。

接下来,在代码真正执行前进行校验(称为延迟校验)。比如刚才举例中提到的异常,就是校验器在校验字段的访问合法性时抛出的。

运行

上面两个阶段检查不了的规则,放到运行时检查:数组越界和类型转换。运行时抛出ArrayIndexOutOfBoundExceptionClassCastException

结语

Java语言层面的安全设计,本身也可以看出设计思路是弥补原有C和C++的部分短板而设计的。主要目标还是防止非法内存访问。但是加入了这些限制也带来了性能的缺失,这本身就是一个trade off。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目目目录 前言 第1章 计算机与网络安全基础 1 1.1 密码学与计算机安全 1 1.2 危险和保护 2 1.3 外围防护 3 1.3.1 防火墙 4 1.3.2 仅仅使用外围防护的不足之处 4 1.4 访问控制与安全模型 4 1.4.1 MAC和DAC模型 5 1.4.2 对数据和信息的访问 5 1.4.3 静态和动态模型 6 1.4.4 关于使用安全模型的几点考虑 6 1.5 密码系统的使用 7 1.5.1 单向散列函数 7 1.5.2 对称密码 8 1.5.3 非对称密码 9 1.6 鉴别 9 1.7 移动代码 10 1.8 Java安全性的适用范围 11 第2章 Java语言的基本安全特点 12 2.1 Java语言和平台 12 2.2 基本安全结构 13 2.3 字节代码验证和类型安全 14 2.4 签名应用小程序 15 2.5 关于安全错误及其修复的简要历史 16 第3章 JDK1.2安全结构 19 3.1 起源 19 3.2 为什么需要一个新型的安全结构 19 3.2.1 关于applet的沙盒模型的局限性 19 3.2.2 策略和实施分离的不彻底性 20 3.2.3 安全核查的不易扩展性 20 3.2.4 对本地安装的applet过于信任 20 3.2.5 内部安全机制的脆弱性 21 3.2.6 总结 21 3.3 java.Security.General SecurityException 21 3.4 安全策略 22 3.5 CodeSource 24 3.5.1 测试等同性和利用隐含 25 3.6 许可权层次 26 3.6.1 java.security.Permission 27 3.6.2 许可权集合 28 3.6.3 java.security.Unresolved Permission 29 3.6.4 java.io.FilePermission 31 3.6.5 java.net.SocketPermission 33 3.6.6 java.security.BasicPermission 35 3.6.7 java.util.PropertyPermission 36 3.6.8 java.lang.RuntimePermission 37 3.6.9 java.awt.AWTPermission 38 3.6.10 java.net.NetPermission 38 3.6.11 java.lang.reflect Reflect Permission 39 3.6.12 java.io .Serializable Permission 39 3.6.13 java.security.Security Permission 39 3.6.14 java.security.AllPermission 40 3.6.15 许可权隐含中的隐含 40 3.7 分配许可权 41 3.8 Protection Domain 42 3.9 安全地加载类 44 3.9.1 类加载器的层次 44 3.9.2 java.lang.ClassLoader和授权 46 3.9.3 java.security.SecureClassLoader 49 3.9.4 java.net.URLClassLoader 49 3.9.5 类的路径 50 3.10 java.lang.SecurityManager 51 3.10.1 使用安全管理器的实例 51 3.10.2 JDK1.2中没有改变的API 52 3.10.3 JDK1.2中禁用的方法 53 3.11 java.security.AccessController 56 3.11.1 AceessController的界面设计 57 3.11.2 基础访问控制算法 57 3.11.3 继承方法 59 3.11.4 扩展带有特权操作的基本算法 59 3.11.5 特权操作的三种类型 61 3.11.6 访问控制语境 63 3.11.7 完整的访问控制算法 64 3.11.8 SecurityManager与 AccessController 65 3.11.9 特权操作发展史 66 3.12 小结 67 第4章 安全

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值