Java
如果要用Java来开发安全程序坦白地说第一步(在学习了Java之后)就是要阅读两本有关Java安全的教材即Gong []和McGraw [](后一本特别要看第节)还应该看一下Sun发布的安全代码指南有一组描述Java安全模型的幻灯片可以从 免费获取
下面是基于Gong []McGraw []和Sun的指南的若干关键要点
不要使用公共域或变量把它们声明为私有的并提供访问函数以限制对它们的访问
除非有很好的理由把方法都设为私有的(如果确实没这样做说清楚其理由)这些非私有的方法必须保护自己因为它们可能会接收到受污染的数据(除非已经用其它方式对它们进行了保护)
避免使用静态域变量这样的变量附着在类(而非类的实例)上而类可以被其它类所定位其结果就是静态域变量可以被其它类找到因此很难保证它们的安全
永远不要把可变对象返回给潜在有恶意的代码(因为代码可能会改变它)注意数组是可变的(即使数组的内容不可变)所以不要返回一个含有敏感数据的内部数组的引用
永远不要直接保存用户给定的可变对象(包括对象的数组)否则用户可以把对象交给安全代码让安全代码检查对象并在安全代码试图使用数据时改变数据应该在内部存储数组前复制它们而且要小心(例如警惕用户编写的复制例程)
不要依赖于初始化有好几种方法给未初始化的对象分配内存
除非有很好的理由应该使每件事都是确定的如果某个类或方法不是确定的攻击者就可以用某种危险而无法预知的方法来扩展它注意作为安全性的交换这会带来可扩展性的丧失
不要在安全性上依赖包的范围若干类如javalang缺省是关闭的而且某些Java虚拟机(JVM)会让你关闭其它包否则Java类是没有关闭的因此攻击者可以向包中引入一个新类并用此新类来访问你以为保护了的信息
不要使用内部类在内部类转换为字节代码时内部类会转换为可以访问包中任意类的类更糟的是被封装类的私有域静悄悄地变成非私有的允许内部类访问!
最小化特权如果可能完全不要请求任何特殊的许可McGraw更进一步地推荐不要标记任何代码我认为可以标记代码(这样用户可以决定只有列表上的发送者可以运行标记过的代码)但在编写程序时要使程序不需要沙箱设置之外的权限如果一定要有更大的权限审读代码就会特别困难
如果一定要标记代码应该把它们都放在一个档案文件里这里最好引用McGraw []的原文
此规则的目的是防止攻击者使用混合匹配攻击构建新applet或库把某些标记类与有恶意的类连接在一起或者把根本意识不到会被一起使用的标记类连接在一起通过把一组类标记在一起就可以使这种攻击更困难现有的代码标记系统在防止混合匹配攻击上做得还不够所以这一规则还不能完全防止此类攻击但使用单个答案没什么坏处
应该使类不可被复制Java的类复制机制允许攻击者不运行构建函数就实例化某个类要使类不可被复制只要在每个类里定义如下方法 public final void clone() throws javalangCloneNotSupportedException {
throw new javalangCloneNotSupportedException();
}
如果确实需要使类可被复制那么可以采用几个保护措施来防止攻击者重新定义复制方法如果是定义自己的复制方法只需要使它是确定的如果不是定义自己的复制方法至少可以通过增加如下内容来防止复制方法被恶意地重载 public final void clone() throws javalangCloneNotSupportedException {
superclone();
}
应该使类不可序列化系列化运行攻击者看到对象的内部状态甚至私有部分要防止这一点需要在类里增加如下方法 private final void writeObject(ObjectOutputStream out)
throws javaioIOException {
throw new javaioIOException(Object cannot be serialized);
}
甚至在序列化没问题的情况下也应该对包含直接处理系统资源的域和包含与地址空间有关信息的域使用临时关键字否则解除类的序列化就会允许不适当的访问可能还需要把敏感信息标识为临时的
如果对类定义了自己的序列化方法就不应该把内部数组传递给需要数组的DataInput/DataOuput方法其理由在于所有的DataInput/DataOuput方法都可以被重载如果某个可序列化的类向某个DataOutput(write(byte [] b))方法直接传递了一个私有数组那么攻击者就可以构建子类ObjectOutputStream并重载write(byte [] b)方法从而可以访问并修改那个私有数组注意缺省的序列化并没有把私有字节数组域暴露给DataInput/DataOutput字节数组方法
应该使类不可被解除序列化即使类不可被序列化它依然可以被解除序列化攻击者可以构建一个字节序列使它碰巧是被解除序列化的某个类实例而且具有攻击者选定的值换句化话说解除序列化是一种公共的构建函数允许攻击者选择对象的状态 显然是一个危险的操作! 要防止这一点需要在类里增加如下方法 private final void readObject(ObjectInputStream in)
throws javaioIOException {
throw new javaioIOException(Class cannot be deserialized);
}
不要通过名称来比较类毕竟攻击者可以用相同的名称定义类而且一不小心就会授予这些类不恰当的权限因此下面是一个判断某个对象是否含有某个给定类的错误方法的例子 if (objgetClass()getName()equals(Foo)) {
如果要判断两个对象是否含有完全相同的类不要对双方使用getClass()并使用==操作符进行比较而应该使用如下形式 if (agetClass() == bgetClass()) {
如果确实需要判断某个对象是否含有某个给定类名需要严格按照规范并确保使用当前名称空间(当前类的ClassLoader所在名称空间)因此应该使用如下形式 if (objgetClass() == thisgetClassLoader()loadClass(Foo)) {
本原则来自McGraw和Felten而且确实是个好原则要补充的是尽可能地避免比较类值通常是个好注意通常最好是尽力设计类的方法和接口从而完全不必要做这些事尽管如此实际上无法完全做到所以知道这些技巧还是很重要的
不要把秘密(密钥密码或算法)存储在代码或数据里有恶意的JVM可以迅速看到这一数据打乱代码并不能在认真的攻击者面前实际隐藏代码