技巧:防范代码的终结器漏洞

标签: 技巧 代码 漏洞 | 发表时间:2011-07-27 10:51 | 作者:Elaine.Ye zhangyijun
出处:http://www.yeeyan.org

原作者:
来源Tip: Secure your code against the finalizer vulnerability
译者Elaine.Ye

在使用终结器(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中,对value参数进行检查的效果被finalize()方法的存在给抵消掉了。

攻击是如何生效的


当然,不可能会有人写出清单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.println(e);
    }
    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。

这种攻击甚至可以用来绕过显式的安全检查,例如,清单5中的Insecure类的设计想法是,如果其运行在一个SecurityManager的管理之下,且调用者没有权限写入当前目录的话就抛出一个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(e);
    }
    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语言规范( Java Language Specification,JLS)的第三版在Java SE 6中实现后,才有了这些避免攻击的方法——使用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:

1. 为对象分配空间。

2. 把对象中的所有实例变量的值设置成它们的默认值,这包括了对象的父类中的实例变量。

3. 对对象参数变量进行赋值。

4. 处理任何显式的或是隐式的构造函数调用(构造函数中的this()或是super()调用)。

5. 初始化类中的变量。

6. 执行构造函数中的其余部分代码。

关键的一点是,构造函数的参数处理是在构造函数内部的任何代码处理之前进行的,这意味着如果你在处理参数时进行验证的话,你可以——以抛出异常的方式——阻止类被终结。

这带来了清单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的构造函数。因此如果异常从checkValue中抛出的话,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(e);
    }
    System.gc();
    System.runFinalization();
    if(vulnerable != null) {
      System.out.println("Invulnerable object " + vulnerable + "
created!");
    } else {
      System.out.println("Attack failed");
    }
  }
}

//增加的部分内容

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

  

如果是使用根据较旧的JLS版本编写的Java 5的话,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

结论


终结器是Java语言中一个令人遗憾的功能,尽管垃圾收集器可以自动地回收任何不再被Java对象使用的内存,但并没有存在这样的机制来回收诸如本地内存、文件描述符或是套接口一类的本地化资源。Java提供的与这些本地化资源做接口的标准库通常会有一个close()方法来允许适当的清理——但它们还需要使用终结器来确保在对象没有被正确关闭时不会有资源泄漏的情况发生。

就其他对象来说,一般情况下最好避免使用终结器,因为并不存在这样的保证,即终结器会在某个时候被执行,甚至到底会不会被执行也不能保证。终结器的存在意味着除非终结器已被运行,否则一个不能被访问的对象不能被垃圾收集,,且这一对象甚至有可能会让更多的对象保持在存活的状态中。这就导致了活动对象数目的增加,从而导致了Java进程的堆使用的增加。

终结器复活了一个预定要被垃圾收集的对象,这一能力显然是终结操作机制运作方式的一个意想不到的后果。现在JVM的较新实现允许你保护代码免遭这一后果所带来的安全隐患的威胁。

下载


描述                    名称      大小    下载方法

这一编程技巧的代码例子     j-fv.zip.zip          4KB               HTTP

关于下载方法的说明

参考资料


学习资料

1. Java Language Specification:有关Java语言的技术参考。

2. Secure Coding Guidelines for the Java Programming Language:阅读这些准则来获得更多关于良好的编程习惯的建议。

3. Effective Java, 2d ed. (Joshua Bloch,Prentice Hall,2008):本书包括了与终结器以及其他的一些重要方面相关的问题的一个讨论。

4. Language designer's notebook:了解Brian Goetz的developerWorks系列文章,这一系列谈论的是影响到Java语言未来的语言设计问题。

5. Java theory and practice:浏览Brian Goetz的这一长期开设的developerWorks系列,该系列是关于Java编程概念、技巧和最佳做法的。

6. developerWorks Java technology zone:可找到数百篇关于Java编程的各个方面的文章。

获取产品和技术

1. 以最适合你的方式来评估IBM的产品:下载产品试用版、在线试用产品、使用云环境中的产品,或是在SOA Sandbox中花费几个小时来学习如何有效地实现面向服务的架构(Service Oriented Architecture)。

讨论

1. Java security:参加developerWorks上帝Java安全论坛。

2. 加入developerWorks社区:在浏览开发者驱动的博客、论坛、讨论组和wiki时与其他developerWorks用户建立联系。

关于作者


  Neil Masson多年来一直都在从事Java语言方面的开发和支持工作,目前他的工作重点是改进Java发行版本的质量和安全。

添加新评论

相关文章:

  语言设计者的笔记:以不伤害为首要原则

  一种减少多线程Java应用的工作队列中的竞争和开销的方法

  Android 和 Java

  第三章 表达式 (复习题及编程练习)

  RJC301:Web开发——Tomcat、GlassFish、OSGi、Tapestry等服务器和框架中的Classloader

相关 [技巧 代码 漏洞] 推荐:

技巧:防范代码的终结器漏洞

- zhangyijun - 译言-每日精品译文推荐
来源Tip: Secure your code against the finalizer vulnerability. 在使用终结器(finalizer)来创建对象时,其可能会给Java代码带来一个脆弱而易受攻击的点. 该漏洞是这一为大家所熟知的使用终结器来复活对象技术的一种变体. 当一个有着finalize()方法的对象变得无法访问时,其就被放置在某个队列中,在晚些时候再加以处理.

软件漏洞分析技巧分享

- - FreeBuf.COM
作者: riusksk【TSRC】. 在日常分析软件漏洞时,经常需要耗费比较长的分析时间,少则几小时,多则数天,甚至更久. 因此,经常总结一些分析技巧是非常有必要的,针对不同的漏洞类型采取不同的分析思路和技巧,可以有效地提高分析速度. 对于一些被曝出来的热门0day,网上一般都会有分析文章,但一般都是“结论性分析”,也就是直接帖漏洞代码,指出哪里出错,而非“思路性分析”.

【转载】从Mozilla的HTML5游戏Browser Quest看代码漏洞

- - HTML5研究小组
声明:作弊并不是什么好事,我不提倡作弊. 我也不想讽刺Mozilla的代码质量(实际上其代码糟透了). 我不是超级黑客,也不想在此唱高调. 只是想演示一下我在Mozilla新游戏上是如何作弊的,以后大家在编写HTML5游戏时要注意避免这些问题. 为了将Bard’s Tale游戏通过磁盘编辑器进行编辑并存入我的Apple 2e,我不得不学习16进制.

struts2框架XSLTResult本地文件代码执行漏洞

- - IT技术博客大学习
标签:   struts2   XSLTResult. 这是个没有人公布过的漏洞,偶尔看代码发现的. 事实上,这段代码用的人不多,需要同时满足两个情况,才可以搞. 我猜测,发出struts2远程代码执行的那个大牛,不可能没发现这么弱智的漏洞. 所以,要么是有原因不能公布,要么就是卖了,那就好,这次我首发,哈哈哈.

Apache警告:Tomcat存在远程代码执行漏洞

- - FreeBuf.COM
开源WEB容器–Apache+Tomcat老版本很容易受到远程代码执行的攻击. Mark Thomas,一位长期致力于Apache+Tomcat的工作者称. “在某种情况下,用户可以上传恶意JSP文件到Tomcat服务器上运行,然后执行命令. JSP的后门可以用来在服务器上任意执行命令. Thomas 今日发出警告称,Tomcat版本7.0.0和7.3.9在发布补丁之前是脆弱的.

USB严重漏洞代码遭公布:情况很糟糕

- - TechWeb 今日焦点 RSS阅读
USB严重漏洞代码遭公布:情况很糟糕. 对于今年早些时候曝光的致命USB漏洞“BadUSB”,两位研究人员近日对其进行了“反向工程”,并公布了相关代码,此举可能使BadUSB漏洞的危害性变得更大. 7月份,研究人员卡斯滕·诺尔(Karsten Nohl)和贾科布·莱尔(Jakob Lell)曾宣布发现一个名为“BadUSB”的严重漏洞,允许攻击者在不被检测到的情况下悄悄在USB设备中植入恶意软件.

Android代码优化小技巧总结

- - 移动开发 - ITeye博客
关注微信号:javalearns   随时随地学Java. 这篇文章主要是介绍了一些小细节的优化技巧,当这些小技巧综合使用起来的时候,对于整个Android App的性能提升还是有作用的,只是不能较大幅度的提升性能而已. 选择合适的算法与数据结构才应该是你首要考虑的因素,在这篇文章中不会涉及这方面.

Google发布Chrome官方扩展DOM Snitch 可发现网页代码漏洞

- 小老虎 - cnBeta.COM
Google今天发布了一个名为DOM Snitch的Chrome官方扩展,它可以让开发者和安全人士在浏览网站时自动识别出不安全的代码,这种扩展的灵感其实是来自于5周之前一家安全公司Mind Security在Firefox上的作品DOMinator,使用这种工具用户可以轻易发现例如XSS、数据泄漏等问题,并指出问题所在的代码段,帮助用户规避以及厂商发现后修补.

PHP代码网站防范SQL注入漏洞攻击的建议

- - BlogJava-qileilove
 所有的网站管理员都会关心网站的安全问题. SQL注入攻击(SQL Injection). 黑客通过SQL注入攻击可以拿到网站. 数据库的访问权限,之后他们就可以拿到网站数据库中所有的数据,恶意的黑客可以通过SQL注入功能篡改数据库中的数据甚至会把数据库中的数据毁坏掉. 做为网络开发者的你对这种黑客行为恨之入骨,当然也有必要了解一下SQL注入这种功能方式的原理并学会如何通过代码来保护自己的网站数据库.

漏洞非小事,金融服务机构如何对抗代码缺陷?

- - FreeBuf互联网安全新媒体平台
在全球金融行业数字化转型与升级的大趋势下,不论是传统银行业的联网业务和手机银行业务,还是移动支付、P2P金融乃至数字货币和区块链,金融行业新技术和新应用层出不穷,银行业、证券业、保险业纷纷都开始依赖应用软件进行业务的拓展及维护. 面对日益激烈的商业竞争,市场不等待,也要求开发者缩短开发和创新的时间.