原文
http://www.ibm.com/developerworks/cn/java/j-fv/index.html
在使用终结器 (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.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。
这种类型的攻击甚至可用于绕过显式安全检查。例如,如果在 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(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 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:
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 的构造函数。所以如果 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(e);
}
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
--------------------------------------------------------------------------------
终结器是 Java 语言的一种不太幸运的功能。尽管垃圾收集器可自动回收 Java 对象不再使用的任何内存,但不存在回收本机内存、文件描述符或套接字等本机资源的机制。Java 提供了与这些本机资源交互的标准库通常有一个 close() 方法,允许执行恰当的清理,但它们也使用了终结器来确保在对象错误关闭时,没有资源泄漏。
对于其他对象,通常最好避免终结器。无法保证终结器将在何时运行,或者甚至它是否会运行。终结器的存在意味着在终结器运行之前,不会对无法访问的对象执行垃圾收集,而且此对象可能使更多对象存活。这导致活动对象数量增加,进而导致 Java 对象的堆使用率增加。
终结器恢复即将被垃圾收集的能力无疑是终结机制工作方式的一种意外后果。较新的 JVM 实现现在保护代码免遭此类安全隐患。
分享到:
相关推荐
4. 避免使用 finalizer:避免使用finalizer来释放资源,使用try-finally语句来释放资源。 五、静态代码分析清单项分类 1. 使用静态代码分析器:使用静态代码分析器来检查代码的质量和安全性。 2. 查看报告:查看...
在我的类中何时需要实现一个完成器?我是否一定要实现完成器,或者只是在我控制着 非托管资源时才需要实现它?我是否一定要在我的完成器中实现 IDisposable 接口?...关键字:finalizer,assembly names,methodinfo
- **重构技巧**:使用IDE提供的工具,如重构向导。 #### (11) 异常处理 - **捕获异常**:合理捕获并处理异常。 - **异常传播**:不要忽略异常,必要时向上层抛出。 #### (12) 资源管理 - **清理操作**:确保资源...
Java代码规范是编程实践中至关重要的一个环节,它旨在提高代码的可读性、可维护性和团队协作效率。Checkstyle是一款非常流行的开源工具,专门用于检查Java代码是否符合预定义的编码规范。它可以帮助开发者在编码过程...
### Java经典代码词汇表速查手册与J2EE相关名词解释 #### 一、Java基本概念及术语 ##### 1. Abstract class (抽象类) - **定义**:抽象类是一种不能被实例化的类,它主要作为其他类的基础,提供部分实现细节。 - *...
4. **堆内存统计**:`jmap -finalizerinfo <pid>`可以查看等待Finalizer线程处理的对象,这有时是内存泄漏的一个迹象。 当获得heap dump文件后,我们通常会使用Eclipse Memory Analysis Tools (MAT)这样的专业工具...
在不断变化的技术环境中,掌握...以上50个JavaScript技巧涵盖了从基础语法到高级特性的方方面面,掌握了这些技巧,将极大地提升你在Web开发中的效率和代码质量。持续学习和实践,是成为优秀JavaScript开发者的关键。
5. **优化代码**:找到内存泄漏的原因后,修改代码以消除泄漏,并重新测试以确保问题已解决。 MAT的使用不仅限于内存泄漏分析,还可以用于优化内存配置,理解应用程序的内存使用模式,以及在复杂环境下的问题定位。...
flexipatch-finalizer是一个自定义预处理器,它使用相同的配置文件,并剥离所有未使用代码的flexipatch构建,从而保留应用了选定补丁的软件构建。 该终结器的示例flexipatch构建可用于: :warning: 请务必注意,...
**安全风险-servlet的反射导致跨站脚本漏洞** - **解释**: 如果servlet直接使用用户输入作为错误消息的一部分发送给客户端,可能会暴露于跨站脚本攻击。 - **建议**: 对用户输入进行过滤或转义处理,确保数据的安全...
第二部分源码分析部分,主要探讨了内存分配器、垃圾回收器和goroutine调度器的工作原理,以及channel、defer和finalizer的实现细节。 第三部分附录涵盖了各种工具的使用,如编译、调试、测试和性能分析,帮助开发者...
`FindBugs` 是一款静态代码分析工具,用于检测 Java 代码中的潜在错误和不良实践。这份报告列举了一些常见的 `FindBugs` 报告问题,让我们逐一解析这些错误并探讨如何解决它们。 1. **IMSE_DONT_CATCH_IMSE**:捕获...
- **规范**: 由于finalizers的不确定性,建议禁用finalizer的使用。 #### 10. 结论 通过遵循Google Java编程规范,开发人员可以编写出更加规范、一致且易于维护的代码。这些规范不仅涵盖了编码格式和样式方面的...
- **Finalizers**: 禁止使用finalizer,因为它们可能导致不可预测的行为和性能问题。 - **Javadoc**: - 提供统一的格式和规范。 - 在适当的地方使用Javadoc来描述类、方法和参数的作用。 - 对于显而易见的方法,...
在给定的压缩包中,可能包含了一系列TC Electronic的著名音频处理插件,如TC Helicon的语音处理插件或者TC Finalizer的母带处理插件。 1. **TX插件**:TX可能是"Texturing"或"Transforming"的缩写,这可能指的是...
从压缩包文件名称“teclast-f5-ubuntu-finalizer-master”来看,这可能是项目的源代码仓库,包含了所有必要的修改和调整。用户或开发者可以通过下载并编译这些源代码,来应用到自己的Teclast F5笔记本上,以获得更好...
这40种JavaScript中常用的使用小技巧将帮助开发者提升编程效率,优化代码质量,以及更好地理解和运用这个强大的脚本语言。以下是一些关键技巧的详细解释: 1. **立即执行函数表达式 (IIFE)**:用于创建独立的作用域...
.NET中的内存管理是自动的,托管代码的内存管理是由CLR(Common Language Runtime)控制的,但是非托管资源不能被自动管理。这意味着当我们使用非托管资源时,需要手动释放内存,以避免内存泄漏。 自动内存管理和GC...