Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » Java SE 综合讨论区 » 学习心得/方法/资源  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 防范代码的 finalizer() 漏洞
烂泥





发贴: 538
积分: 72
于 2011-10-15 08:57 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
这篇文章还是很不错的,可以帮助理解finalizer
http://www.ibm.com/developerworks/cn/java/j-fv/index.html?ca=drs-

在使用终结器 (finalizer) 来创建对象时,其可能会给 Java 代码带来漏洞。该漏洞是使用终结器来恢复对象的著名技术的一种变体。当包含 finalize() 方法的对象变得无法访问时,它会被放入一个将在以后某个时刻处理的队列上。本文解释此类攻击的工作原理,介绍如何保护代码免遭此类攻击。所有代码示例都可供 下载。

终结器的理念是允许 Java 方法释放任何需要返回到操作系统的本机资源。遗憾的是,任何 Java 代码都可以在终结器中运行,可以使用类似清单 1 的代码:

清单 1. 一个可恢复的类

        
public class Zombie {
static Zombie zombie;

public void finalize() {
zombie = this;
}
}

在调用 Zombie 终结器时,它获取被终结的对象(由 this 引用)并将它存储在静态 zombie 变量中。现在该对象又是可访问的,其不能被垃圾收集。

此代码的一种存在更大隐患的版本甚至允许恢复部分构造的对象。即使对象在初始化过程中不能通过正确性检查,其仍能够被终结器创建出来,如清单 2 所示:

清单 2. 创建一个非法的类

        
public class Zombie2 {
static Zombie2 zombie;
int value;

public Zombie2(int value) {
if(value < 0) {
throw new IllegalArgumentException("Negative Zombie2 value");
}
this.value = value;
}
public void finalize() {
zombie = this;
}
}

在 清单 2 中,finalize() 方法的存在使对 value 参数的检查变得无效。

该攻击的工作原理

当然,可能没有人会编写类似 清单 2 这样的代码。但如果类被继承了的话,则可能出现漏洞,如清单 3 所示:

清单 3. 一个易受攻击的类

        
class Vulnerable {
Integer value = 0;

Vulnerable(int value) {
if(value <= 0) {
throw new IllegalArgumentException("Vulnerable value must be positive");
}
this.value = value;
}
@Override
public String toString() {
return(value.toString());
}
}

清单 3 中的 Vulnerable 类用于预防设置非正的 value 值。此意图被 AttackVulnerable() 方法破坏,如清单 4 所示:

清单 4. 破坏 Vulnerable 类的类

        
class AttackVulnerable extends Vulnerable {
static Vulnerable vulnerable;

public AttackVulnerable(int value) {
super(value);
}

public void finalize() {
vulnerable = this;
}

public static void main(String[] args) {
try {
new AttackVulnerable(-1);
} catch(Exception e) {
System.out.printlnEnvelope;
}
System.gc();
System.runFinalization();
if(vulnerable != null) {
System.out.println("Vulnerable object " + vulnerable + " created!");
}
}
}

AttackVulnerable 类的 main() 方法试图创建一个新的 AttackVulnerable 对象实例。因为 value 的值超出了范围,所以抛出了一个被 catch 块捕获的异常。System.gc() 和 System.runFinalization() 的调用促使 VM 运行一个垃圾回收周期并运行一些终结器。这些调用不是成功攻击的必要条件,但它们可用来说明攻击的最终结果,那就是创建了一个包含无效值的 Vulnerable 对象。

运行测试用例会得到以下结果:

java.lang.IllegalArgumentException: Vulnerable value must be positive

Vulnerable object 0 created!

为什么 Vulnerable 的值是 0,而不是 -1?请注意,在 清单 3 中的 Vulnerable 构造函数中,在执行参数检查后才会给 value 赋值。所以 value 拥有自己的初始值,在本例中为 0。

这种类型的攻击甚至可用于绕过显式安全检查。例如,如果在 SecurityManager 下运行并且调用方没有权限向当前目录写入数据,就可以使用清单 5 中的 Insecure 类来抛出 SecurityException:

清单 5. Insecure 类

        
import java.io.FilePermission;

public class Insecure {
Integer value = 0;

public Insecure(int value) {
SecurityManager sm = System.getSecurityManager();
if(sm != null) {
FilePermission fp = new FilePermission("index", "write");
sm.checkPermission(fp);
}
this.value = value;
}
@Override
public String toString() {
return(value.toString());
}
}

清单 5 中的 Insecure 类可通过与前面相同的方式攻击,如清单 6 中的 AttackInsecure 类所示:

清单 6. 攻击 Insecure 类

        
public class AttackInsecure extends Insecure {
static Insecure insecure;

public AttackInsecure(int value) {
super(value);
}

public void finalize() {
insecure = this;
}

public static void main(String[] args) {
try {
new AttackInsecure(-1);
} catch(Exception e) {
System.out.printlnEnvelope;
}
System.gc();
System.runFinalization();
if(insecure != null) {
System.out.println("Insecure object " + insecure + " created!");
}
}
}

在 SecurityManager 下运行 清单 6 中的代码会得到以下输出:

java -Djava.security.manager AttackInsecure
java.security.AccessControlException: Access denied (java.io.FilePermission index write)
Insecure object 0 created!

回页首

如何避免攻击

在 Java SE 6 中实现第三版 Java 语言规范 (JLS) 之前,避免攻击的仅有方式是使用 initialized 标志、禁止子类化或创建 final 终结器)并不是令人满意的解决方案。

使用 initialized 标志

一种避免攻击的方式是使用 initialized 标志,在正确创建对象之后它被设置为 true。该类中的每个方法首先检查是否设置了 initialized,如果没有设置则抛出一个异常。这样的代码编写起来很无趣,很容易被意外省略,也无法阻止攻击者子类化该方法。

预防子类化

您可以将所创建的类声明为 final。这意味着没有人可创建该类的子类,这会阻止攻击生效。但是,此技术降低了灵活性,无法扩展该类来特殊化它或增加额外的功能。

创建一个 final 终结器

可以为所创建的类创建一个终结器并将它声明为 final。这意味着该类的任何子类都无法声明终结器。此方法的缺点是,终结器的存在意味着对象的存活期比其他情况下更长。

一种新的、更好的方式

为了更容易避免此类攻击,而无需引入额外的代码或限制,Java 设计人员修改了 JLS(参见 参考资料),声明如果在构造 java.lang.Object 之前在构造函数中抛出了一个异常,该方法的 finalize() 方法将不会执行。

但是如何在构造 java.lang.Object 之前抛出异常呢?毕竟,任何构造函数中的第一行都必须是对 this() 或 super() 的调用。如果构造函数没有包含这样的显式调用,将隐式添加对 super() 的调用。所以在创建对象之前,必须构造相同类或其超类的另一个对象。这最终导致了对 java.lang.Object 本身的构造,然后在执行所构造方法的任何代码之前,构造所有子类。

要理解如何在构造 java.lang.Object 之前抛出异常,需要理解准确的对象构造顺序。JLS 明确给出了这一顺序。

当创建对象时,JVM:

为对象分配空间。
将对象中所有的实例变量设置为它们的默认值。这包括对象超类中的实例变量。
分配对象的参数变量。
处理任何显式或隐式构造函数调用(在构造函数中调用 this() 或 super())。
初始化类中的变量。
执行构造函数的剩余部分。

重要的是构造函数的参数在处理构造函数内的任何代码之前被处理。这意味着,如果在处理参数时执行验证,可以通过抛出异常预防类被终结。

这带来了 清单 3 的 Vulnerable 类的一个新版本,如清单 7 所示:

清单 7. Invulnerable 类

        
class Invulnerable {
int value = 0;

Invulnerable(int value) {
this(checkValues(value));
this.value = value;
}

private Invulnerable(Void checkValues) {}

static Void checkValues(int value) {
if(value <= 0) {
throw new IllegalArgumentException("Invulnerable value must be positive");
}
return null;
}

@Override
public String toString() {
return(Integer.toString(value));
}
}

在 清单 7 中,Invulnerable 的公共构造函数调用一个私有构造函数,而后者调用 checkValues 方法来创建其参数。此方法在构造函数执行调用来构造其超类之前调用,该构造函数是 Object 的构造函数。所以如果 checkValues 中抛出了一个异常,那么将不会终结 Invulnerable 对象。

清单 8 中的代码尝试攻击 Invulnerable:

清单 8. 尝试破坏 Invulnerable 类

        
class AttackInvulnerable extends Invulnerable {
static Invulnerable vulnerable;

public AttackInvulnerable(int value) {
super(value);
}

public void finalize() {
vulnerable = this;
}

public static void main(String[] args) {
try {
new AttackInvulnerable(-1);
} catch(Exception e) {
System.out.printlnEnvelope;
}
System.gc();
System.runFinalization();
if(vulnerable != null) {
System.out.println("Invulnerable object " + vulnerable + "
created!");
} else {
System.out.println("Attack failed");
}
}
}

with the addition of

} else {
System.out.println("Attack failed");

使用 Java 5(针对较旧 JLS 版本而编写),创建了一个 Invulnerable 对象:

java.lang.IllegalArgumentException: Invulnerable value must be positive
Invulnerable object 0 created!

在 Java SE 6(自 Oracle 的 JVM 的通用版本和 IBM 的 JVM 的 SR9 开始)及以后的规范中,不会创建该对象:

java.lang.IllegalArgumentException: Invulnerable value must be positive
Attack failed




flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923