Java编写过程中安全问题解决指南Word文件下载.docx
《Java编写过程中安全问题解决指南Word文件下载.docx》由会员分享,可在线阅读,更多相关《Java编写过程中安全问题解决指南Word文件下载.docx(13页珍藏版)》请在冰豆网上搜索。
protectedStringname;
Test(){
id=1;
name="
helloworld"
;
}
//code
publicclassMyClassextendsTest{
publicvoidmethodIllegalSet(Stringname){
this.name=name;
//thisshouldnotbeallowed
publicstaticvoidmain(String[]args){
Testobj=newTest();
obj.id=123;
MyClassmc=newMyClass();
mc.methodIllegalSet("
IllegalSetValue"
);
建议
一般来说,应该使用取值方法而不是public变量。
按照具体问题具体对待的原则,在确定哪些变量特别重要因而应该声明为private时,请将编码的方便程度及成本同安全性需要加以比较。
清单2演示了以下列方式来使之安全的代码:
清单2.不带有public变量的代码
privateintid;
privateStringname;
publicvoidsetId(intid){
this.id=id;
publicvoidsetName(Stringname){
publicintgetId(){
returnid;
publicStringgetName(){
returnname;
让每个类和方法都为final
不允许扩展的类和方法应该声明为final。
这样做防止了系统外的代码扩展类并修改类的行为。
仅仅将类声明为非public并不能防止攻击者扩展类,因为仍然可以从它自己的包内访问该类。
让每个类和方法都成为final,除非有足够的理由不这样做。
按此建议,我们要求您放弃可扩展性,虽然它是使用诸如Java语言之类的面向对象语言的主要优点之一。
在试图提供安全性时,可扩展性却成了您的敌人;
可扩展性只会为攻击者提供更多给您带来麻烦的方法。
没有显式地标注为public、private或protected的类、方法和变量在它们自己的包内是可访问的。
如果Java包不是封闭的,那么攻击者就可以向包内引入新类并使用该新类来访问您想保护的内容。
诸如java.lang之类的一些包缺省是封闭的,一些JVM也让您封闭自己的包。
然而,您最好假定包是不封闭的。
从软件工程观点来看,包作用域具有重要意义,因为它可以阻止对您想隐藏的内容进行偶然的、无意中的访问。
但不要依靠它来获取安全性。
应该将类、方法和变量显式标注为public、private或protected中适合您特定需求的那种。
克隆允许绕过构造器而轻易地复制类实例。
即使您没有有意使类可克隆,外部源仍然可以定义您的类的子类,并使该子类实现java.lang.Cloneable。
这就让攻击者创建了您的类的新实例。
拷贝现有对象的内存映象生成了新的实例;
虽然这样做有时候是生成新对象的可接受方法,但是大多数时候是不可接受的。
清单3说明了因为可克隆而暴露的代码:
清单3.可克隆代码
classMyClass{
publicMyClass(){
id=1;
name="
HaryPorter"
publicMyClass(intid,Stringname){
this.id=id;
this.name=name;
publicvoiddisplay(){
System.out.println("
Id="
+id+"
"
+"
Name="
+name);
//hackerscodetoclonetheuserclass
publicclassHackerextendsMyClassimplementsCloneable{
Hackerhack=newHacker();
try{
MyClasso=(MyClass)hack.clone();
o.display();
catch(CloneNotSupportedExceptione){
e.printStackTrace();
要防止类被克隆,可以将清单4中所示的方法添加到您的类中:
清单4.使您的代码不可克隆
publicfinalObjectclone()
throwsjava.lang.CloneNotSupportedException{
thrownewjava.lang.CloneNotSupportedException();
如果想让您的类可克隆并且您已经考虑了这一选择的后果,那么您仍然可以保护您的类。
要做到这一点,请在您的类中定义一个为final的克隆方法,并让它依赖于您的一个超类中的一个非final克隆方法,如清单5中所示:
清单5.以安全的方式使您的代码可克隆
throwsjava.lang.CloneNotSupportedException{
super.clone();
类中出现clone()方法防止攻击者重新定义您的clone方法。
序列化允许将类实例中的数据保存在外部文件中。
闯入代码可以克隆或复制实例,然后对它进行序列化。
序列化是令人担忧的,因为它允许外部源获取对您的对象的内部状态的控制。
这一外部源可以将您的对象之一序列化成攻击者随后可以读取的字节数组,这使得攻击者可以完全审查您的对象的内部状态,包括您标记为private的任何字段。
它也允许攻击者访问您引用的任何对象的内部状态。
要防止类中的对象被序列化,请在类中定义清单6中的writeObject()方法:
清单6.防止对象序列化
privatefinalvoidwriteObject(ObjectOutputStreamout)
throwsjava.io.NotSerializableException{
thrownewjava.io.NotSerializableException("
Thisobjectcannot
beserialized"
通过将writeObject()方法声明为final,防止了攻击者覆盖该方法。
通过使用逆序列化,攻击者可以用外部数据或字节流来实例化类。
不管类是否可以序列化,都可以对它进行逆序列化。
外部源可以创建逆序列化成类实例的字节序列。
这种可能为您带来了大量风险,因为您不能控制逆序列化对象的状态。
请将逆序列化作为您的对象的另一种公共构造器?
一种您无法控制的构造器。
要防止对对象的逆序列化,应该在您的类中定义清单7中的readObject()方法:
清单7.防止对象逆序列化
privatefinalvoidreadObject(ObjectInputStreamin)
bedeserialized"
通过将该方法声明为final,防止了攻击者覆盖该方法。
您可能会尝试将诸如加密密钥之类的秘密存放在您的应用程序或库的代码。
对于你们开发人员来说,这样做通常会把事情变得更简单。
任何运行您的代码的人都可以完全访问以这种方法存储的秘密。
没有什么东西可以防止心怀叵测的程序员或虚拟机窥探您的代码并了解其秘密。
可以以一种只可被您解密的方式将秘密存储在您代码中。
在这种情形下,秘密只在于您的代码所使用的算法。
这样做没有多大坏处,但不要洋洋得意,认为这样做提供了牢固的保护。
您可以遮掩您的源代码或字节码?
也就是,以一种为了解密必须知道加密格式的方法对源代码或字节码进行加密?
但攻击者极有可能能够推断出加密格式,对遮掩的代码进行逆向工程从而揭露其秘密。
这一问题的一种可能解决方案是:
将敏感数据保存在属性文件中,无论什么时候需要这些数据,都可以从该文件读取。
如果数据极其敏感,那么在访问属性文件时,您的应用程序应该使用一些加密/解密技术。
从事某个项目的某个心怀叵测的开发人员可能故意引入易受攻击的代码,打算日后利用它。
这样的代码在初始化时可能会启动一个后台进程,该进程可以为闯入者开后门。
它也可以更改一些敏感数据。
这样的恶意代码有三类:
类中的main方法
定义过且未使用的方法
注释中的死代码
入口点程序可能很危险而且有恶意。
通常,Java开发人员往往在其类中编写main()方法,这有助于测试单个类的功能。
当类从测试转移到生产环境时,带有main()方法的类就成为了对应用程序的潜在威胁,因为闯入者将它们用作入口点。
请检查代码中是否有未使用的方法出现。
这些方法在测试期间将会通过所有的安全检查,因为在代码中不调用它们?
但它们可能含有硬编码在它们内部的敏感数据(虽然是测试数据)。
引入一小段代码的攻击者随后可能调用这样的方法。
避免最终应用程序中的死代码(注释内的代码)。
如果闯入者去掉了对这样的代码的注释,那么代码可能会影响系统的功能性。
可以在清单8中看到所有三种类型的恶意代码的示例:
清单8.潜在恶意的Java代码
publicvoidunusedMethod(){
//codewrittentoharmthesystem
publicvoidusedMethod(){
//unusedMethod();
//codeincommentputwithbadintentions,
//mightaffectthesystemifuncommented
//intx=100;
//x=x+10;
//Codeincomment,mightaffectthe
//functionalityofthesystemifuncommented
应该将(除启动应用程序的main()方法之外的)main()方法、未使用的方法以及死代码从应用程序代码中除去。
在软件交付使用之前,主要开发人员应该对敏感应用程序进行一次全面的代码评审。
应该使用“Stub”或“dummy”类代替main()方法以测试应用程序的功能。
对付中等严重性暴露的技巧
请遵循下列建议以避免中等严重性静态安全性暴露:
不要依赖初始化
不要通过名称来比较类
不要使用内部类
您可以不运行构造器而分配对象。
这些对象使用起来不安全,因为它们不是通过构造器初始化的。
在初始化时验证对象确保了数据的完整性。
例如,请想象为客户创建新帐户的Account对象。
只有在Account期初余额大于0时,才可以开设新帐户。
可以在构造器里执行这样的验证。
有些人未执行构造器而创建Account对象,他可能创建了一个具有一些负值的新帐户,这样会使系统不一致,容易受到进一步的干预。
在使用对象之前,请检查对象的初始化过程。
要做到这一点,每个类都应该有一个在构造器中设置的私有布尔标志,如清单9中的类所示。
在每个非static方法中,代码在任何进一步执行之前都应该检查该标志的值。
如果该标志的值为true,那么控制应该进一步继续;
否则,控制应该抛出一个例外并停止执行。
那些从构造器调用的方法将不会检查初始化的变量,因为在调用方法时没有设置标志。
因为这些方法并不检查标志,所以应该将它们声明为private以防止用户直接访问它们。
清单9.使用布尔标志以检查初始化过程
publicclassMyClass{
privatebooleaninitialized=false;
//Othervariables
publicMyClass(){
//variableinitialization
method1();
initialized=true;
privatevoidmethod1(){//noneedtocheckforinitializationvariable
publicvoidmethod2(){
if(initialized==true){
//proceedwiththebusinesslogic
else{
thrownewException("
IllegalStateOftheobject"
}catch(Exceptione){
如果对象由逆序列化进行初始化,那么上面讨论的验证机制将难以奏效,因为在该过程中并不调用构造器。
在这种情况下,类应该实现ObjectInputValidation接口:
清单10.实现ObjectInputValidation
interfacejava.io.ObjectInputValidation{
publicvoidvalidateObject()throwsInvalidObjectException;
所有验证都应该在validateObject()方法中执行。
对象还必须调用ObjectInputStream.RegisterValidation()方法以为逆序列化对象之后的验证进行注册。
RegisterValidation()的第一个参数是实现validateObject()的对象,通常是对对象自身的引用。
注:
任何实现validateObject()的对象都可能充当对象验证器,但对象通常验证它自己对其它对象的引用。
RegisterValidation()的第二个参数是一个确定回调顺序的整数优先级,优先级数字大的比优先级数字小的先回调。
同一优先级内的回调顺序则不确定。
当对象已逆序列化时,ObjectInputStream按照从高到低的优先级顺序调用每个已注册对象上的validateObject()。
不要通过名称来比较类
有时候,您可能需要比较两个对象的类,以确定它们是否相同;
或者,您可能想看看某个对象是否是某个特定类的实例。
因为JVM可能包括多个具有相同名称的类(具有相同名称但却在不同包内的类),所以您不应该根据名称来比较类。
如果根据名称来比较类,您可能无意中将您不希望授予别人的权利授予了闯入者的类,因为闯入者可以定义与您的类同名的类。
例如,请假设您想确定某个对象是否是类com.bar.Foo的实例。
清单11演示了完成这一任务的错误方法:
清单11.比较类的错误方法
if(obj.getClass().getName().equals("
Foo"
))//Wrong!
//objectsclassisnamedFoo
}else{
//object'
sclasshassomeothername
在那些非得根据名称来比较类的情况下,您必须格外小心,必须确保使用了当前类的ClassLoader的当前名称空间,如清单12中所示:
清单12.比较类的更好方法
if(obj.getClass()==this.getClassLoader().loadClass("
com.bar.Foo"
)){
sclassisequalto
//theclassthatthisclasscalls"
sclassisnotequaltotheclassthat
//thisclasscalls"
然而,比较类的更好方法是直接比较类对象看它们是否相等。
例如,如果您想确定两个对象a和b是否属同一个类,那么您就应该使用清单13中的代码:
清单13.直接比较对象来看它们是否相等
if(a.getClass()==b.getClass()){
//objectshavethesameclass
//objectshavedifferentclasses
尽可能少用直接名称比较。