天天看点

技巧:防范代码的 finalizer() 漏洞 一种预防创建无效类的模式

简介: 您的 java 代码有可能会因终结操作带来的漏洞而易受到攻击,了解这一漏洞是如何起作用的,并学习如何通过修改代码来防止此类攻击。

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

清单 1. 一个可恢复的类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<code>public</code> <code>class</code> <code>zombie {</code>

<code></code><code>static</code> <code>zombie zombie;</code>

<code></code><code>public</code> <code>void</code> <code>finalize() {</code>

<code></code><code>zombie =</code><code>this</code><code>;</code>

<code></code><code>}</code>

<code></code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args) {</code>

<code></code><code>zombie demo =</code><code>new</code> <code>zombie();</code>

<code></code><code>system.out.println(demo.zombie);</code>

<code></code><code>demo.finalize();</code>

<code>}</code>

运行结果:

null

zombie@18a992f

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

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

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

15

16

17

18

19

20

21

22

<code>public</code> <code>class</code> <code>zombie2 {</code>

<code></code><code>static</code> <code>zombie2 zombie;</code>

<code></code><code>int</code> <code>value;</code>

<code></code><code>public</code> <code>zombie2(</code><code>int</code> <code>value) {</code>

<code></code><code>if</code> <code>(value &lt;</code><code>0</code><code>) {</code>

<code></code><code>throw</code> <code>new</code> <code>illegalargumentexception(</code><code>"negative zombie2 value"</code><code>);</code>

<code></code><code>this</code><code>.value = value;</code>

<code></code><code>zombie2 demo=</code><code>new</code> <code>zombie2(</code><code>1</code><code>);</code>

<code></code><code>system.out.println(demo.value);</code>

<code></code><code>demo.value=-</code><code>1</code><code>;</code>

-1

<code>finalize()</code> 方法的存在使对 <code>value</code> 参数的检查变得无效

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

<code></code>

<code>class</code> <code>vulnerable {</code>

<code></code><code>integer value =</code><code>0</code><code>;</code>

<code></code><code>vulnerable(</code><code>int</code> <code>value) {</code>

<code></code><code>if</code><code>(value &lt;=</code><code>0</code><code>) {</code>

<code></code><code>throw</code> <code>new</code> <code>illegalargumentexception(</code><code>"vulnerable value must be positive"</code><code>);</code>

<code></code><code>@override</code>

<code></code><code>public</code> <code>string tostring() {</code>

<code></code><code>return</code><code>(value.tostring());</code>

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

清单 4. 破坏 <code>vulnerable</code> 类的类

23

24

<code>class</code> <code>attackvulnerable</code><code>extends</code> <code>vulnerable {</code>

<code></code><code>static</code> <code>vulnerable vulnerable;</code>

<code></code><code>public</code> <code>attackvulnerable(</code><code>int</code> <code>value) {</code>

<code></code><code>super</code><code>(value);</code>

<code></code><code>vulnerable =</code><code>this</code><code>;</code>

<code></code><code>try</code> <code>{</code>

<code></code><code>new</code> <code>attackvulnerable(-</code><code>1</code><code>);</code>

<code></code><code>}</code><code>catch</code><code>(exception e) {</code>

<code></code><code>system.out.println(e);</code>

<code></code><code>system.gc();</code>

<code></code><code>system.runfinalization();</code>

<code></code><code>if</code><code>(vulnerable !=</code><code>null</code><code>) {</code>

<code></code><code>system.out.println(</code><code>"vulnerable object "</code> <code>+ vulnerable +</code><code>" created!"</code><code>);</code>

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

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

<code>java.lang.illegalargumentexception: vulnerable value must be positive</code>

<code>vulnerable object</code><code>0</code> <code>created!</code>

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

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

清单 5. <code>insecure</code> 类

<b></b>

<code>import</code> <code>java.io.filepermission;</code>

<code>public</code> <code>class</code> <code>insecure {</code>

<code></code><code>public</code> <code>insecure(</code><code>int</code> <code>value) {</code>

<code></code><code>securitymanager sm = system.getsecuritymanager();</code>

<code></code><code>if</code><code>(sm !=</code><code>null</code><code>) {</code>

<code></code><code>filepermission fp =</code><code>new</code> <code>filepermission(</code><code>"index"</code><code>,</code><code>"write"</code><code>);</code>

<code></code><code>sm.checkpermission(fp);</code>

清单 5 中的 <code>insecure</code> 类可通过与前面相同的方式攻击,如清单 6 中的 <code>attackinsecure</code> 类所示:

清单 6. 攻击 <code>insecure</code> 类

<code>public</code> <code>class</code> <code>attackinsecure</code><code>extends</code> <code>insecure {</code>

<code></code><code>static</code> <code>insecure insecure;</code>

<code></code><code>public</code> <code>attackinsecure(</code><code>int</code> <code>value) {</code>

<code></code><code>insecure =</code><code>this</code><code>;</code>

<code></code><code>new</code> <code>attackinsecure(-</code><code>1</code><code>);</code>

<code></code><code>if</code><code>(insecure !=</code><code>null</code><code>) {</code>

<code></code><code>system.out.println(</code><code>"insecure object "</code> <code>+ insecure +</code><code>" created!"</code><code>);</code>

在 <code>securitymanager</code> 下运行 清单 6 中的代码会得到以下输出:

<code>java -djava.security.manager attackinsecure</code>

<code>java.security.accesscontrolexception: access denied (java.io.filepermission index write)</code>

<code>insecure object</code><code>0</code> <code>created!</code>

如何避免攻击

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

使用 <code>initialized</code> 标志

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

预防子类化

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

创建一个 <code>final</code> 终结器

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

一种新的、更好的方式

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

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

当创建对象时,jvm:

为对象分配空间。

将对象中所有的实例变量设置为它们的默认值。这包括对象超类中的实例变量。

分配对象的参数变量。

处理任何显式或隐式构造函数调用(在构造函数中调用 <code>this()</code> 或 <code>super()</code>)。

初始化类中的变量。

执行构造函数的剩余部分。

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

这带来了 清单 3 的 <code>vulnerable</code> 类的一个新版本,如清单 7 所示:

清单 7. <code>invulnerable</code> 类

<code>class</code> <code>invulnerable {</code>

<code></code><code>int</code> <code>value =</code><code>0</code><code>;</code>

<code></code><code>invulnerable(</code><code>int</code> <code>value) {</code>

<code></code><code>this</code><code>(checkvalues(value));</code>

<code></code><code>private</code> <code>invulnerable(void checkvalues) {}</code>

<code></code><code>static</code> <code>void checkvalues(</code><code>int</code> <code>value) {</code>

<code></code><code>throw</code> <code>new</code> <code>illegalargumentexception(</code><code>"invulnerable value must be positive"</code><code>);</code>

<code></code><code>return</code> <code>null</code><code>;</code>

<code></code><code>return</code><code>(integer.tostring(value));</code>

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

清单 8 中的代码尝试攻击 <code>invulnerable</code>:

清单 8. 尝试破坏 <code>invulnerable</code> 类

25

26

27

28

29

30

31

32

33

<code>class</code> <code>attackinvulnerable</code><code>extends</code> <code>invulnerable {</code>

<code></code><code>static</code> <code>invulnerable vulnerable;</code>

<code></code><code>public</code> <code>attackinvulnerable(</code><code>int</code> <code>value) {</code>

<code></code><code>new</code> <code>attackinvulnerable(-</code><code>1</code><code>);</code>

<code></code><code>system.out.println(</code><code>"invulnerable object "</code> <code>+ vulnerable + "</code>

<code>created!");</code>

<code></code><code>}</code><code>else</code> <code>{</code>

<code></code><code>system.out.println(</code><code>"attack failed"</code><code>);</code>

<code>with the addition of</code>

使用 java 5(针对较旧 jls 版本而编写),创建了一个 <code>invulnerable</code> 对象:

<code>java.lang.illegalargumentexception: invulnerable value must be positive</code>

<code>invulnerable object</code><code>0</code> <code>created!</code>

在 java se 6(自 oracle 的 jvm 的通用版本和 ibm 的 jvm 的 sr9 开始)及以后的规范中,不会创建该对象

<code>attack failed</code>

<a>结束语</a>

终结器是 java 语言的一种不太幸运的功能。尽管垃圾收集器可自动回收 java 对象不再使用的任何内存,但不存在回收本机内存、文件描述符或套接字等本机资源的机制。java 提供了与这些本机资源交互的标准库通常有一个 <code>close()</code> 方法,允许执行恰当的清理,但它们也使用了终结器来确保在对象错误关闭时,没有资源泄漏。

对于其他对象,通常最好避免终结器。无法保证终结器将在何时运行,或者甚至它是否会运行。终结器的存在意味着在终结器运行之前,不会对无法访问的对象执行垃圾收集,而且此对象可能使更多对象存活。这导致活动对象数量增加,进而导致 java 对象的堆使用率增加。

终结器恢复即将被垃圾收集的能力无疑是终结机制工作方式的一种意外后果。较新的 jvm 实现现在保护代码免遭此类安全隐患。