防范代码的 finalizer() 漏洞[转帖]_Android, Python及开发编程讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Android, Python及开发编程讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3806 | 回复: 0   主题: 防范代码的 finalizer() 漏洞[转帖]        下一篇 
luqunfang
注册用户
等级:少校
经验:1219
发帖:74
精华:0
注册:2012-6-25
状态:离线
发送短消息息给luqunfang 加好友    发送短消息息给luqunfang 发消息
发表于: IP:您无权察看 2012-7-24 13:52:45 | [全部帖] [楼主帖] 楼主

在使用终结器 (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.printl;
      }
      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.println;
      }
      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.println;
      }
      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




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论