为什么 Java 终结器存在安全问题?

Rah*_*hul 3 java garbage-collection memory-management finalizer effective-java

我正在阅读Joshua Bloch 的《Effective Java》。第 8项:避免第 2 章定型剂和清洁剂定型剂和清洁剂中,他指出:

\n
\n

终结器有一个严重的安全问题:它们使您的类容易受到终结器攻击。终结器攻击背后的想法很简单:如果从构造函数或其序列化\nequivalents\xe2\x80\x94中抛出\n异常,则readObjectreadResolve方法(第12)\xe2\x80\x94\n恶意子类的终结器可以在部分构造的\n对象上运行,而该对象应该有\xe2\x80\x9cd。\xe2\x80\x9d 这个终结器可以记录\n对静态字段中的对象,防止其\n被垃圾收集。一旦记录了格式错误的对象,就可以简单地调用该对象上的任意方法,而这些方法本来就不应该被允许存在。从构造函数抛出\n异常应该足以阻止\n对象的存在;在存在终结器的情况下,情况并非如此。\n此类攻击可能会产生可怕的后果。Final 类不会受到终结器攻击,因为没有人可以编写 Final 类的恶意子类。

\n
\n

首先,我知道自 Java 18 以来终结器已被弃用。尽管如此,我认为了解此决定背后的原因很重要。我对上面摘录的理解如下:

\n
    \n
  1. 终结器是不确定的。
  2. \n
  3. 恶意子类可以在部分构造的损坏的超类对象上运行其终结器方法。
  4. \n
  5. 将损坏对象的引用移动到静态字段不会让 JVM 进行垃圾收集。
  6. \n
  7. 攻击者可以使用这个应该在 vine\xe2\x80\x9d 上死掉的对象,并按照他们的意愿行事。因此,存在安全缺陷。
  8. \n
\n

其次,我希望我对这个问题的概念理解是正确的。然而,Bloch 尚未在具体的代码示例中演示这个问题。也许是因为他不想让我们搞乱Object.

\n

您能用代码向我演示一下吗?

\n

例如,如果我有一个超类:

\n
/** Superclass */\npublic class DemoSecurityProblem {\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后通过继承或组合来子类:

\n
public class MaliciousSubClass extends DemoSecurityProblem {\n    DemoSecurityProblem demoSecurityProblem = new DemoSecurityProblem();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

攻击者如何通过终结机制利用这一点?

\n

多谢!

\n

Hol*_*ger 5

你的描述基本上是正确的,但事情过于复杂。不需要在static变量中存储任何内容;一旦finalize()调用该方法,该对象就已经复活,因为调用对象上的方法意味着调用可以访问该对象的代码。

\n

将对象引用存储在变量中是一种将生命周期扩展到方法执行之外的方法finalize(),但这对于攻击来说不是必需的。此外,static攻击者还可以将子类设置为内部类,并将引用存储在仍然可访问的外部对象中,而不是使用变量。

\n

所以下面的程序已经足以说明问题了

\n
public class FinalizerAttackExample {\n    public static void main(String[] args) throws InterruptedException {\n      try {\n          new MaliciousSubclass();\n      } catch(SecurityException ex) {\n          System.out.println("wouldn\'t get hands on a ResourceClass instance");\n      }\n      System.gc();\n      Thread.sleep(2000);\n    }\n\n    static class ResourceClass {\n        ResourceClass() {\n            if(!checkCaller()) throw new SecurityException();\n        }\n        public void criticalAction() {\n            System.out.println("ResourceClass.criticalAction()");\n        }\n    }\n\n    /** For our demonstration, all callers are invalid */\n    static boolean checkCaller() {\n        return false;\n    }\n\n    static class MaliciousSubclass extends ResourceClass {\n        @Override\n        protected void finalize() {\n            System.out.println("see, I got hands on " + this);\n            criticalAction();\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

虽然垃圾收集是不确定的,并且通常不能保证终结器的执行,但此示例将打印

\n
wouldn\'t get hands on a ResourceClass instance\nsee, I got hands on FinalizerAttackExample$MaliciousSubclass@7ad74083\nResourceClass.criticalAction()\n
Run Code Online (Sandbox Code Playgroud)\n

在很多实现上,证明criticalAction()可以在一个不应存在的对象上调用它,因为构造函数抛出了异常。

\n

  • @xenoterracide这不仅仅是调用一个方法,它是关于使用一个不应该存在的对象,因为构造函数抛出了异常。有问题的对象可能具有无效状态或调用者通常无法访问的状态。当然,这是假设具有不同权限的代码在同一个 JVM 上运行,这在 Applet 或类似技术出现时更为相关。如今,甚至“SecurityManager”也被标记为“*已弃用,将被删除*”,因此当您说“只是不要让不受信任的代码在同一个 JVM 中运行”时,您就走在了正确的轨道上。 (2认同)