32

最近,我项目的安全团队发布了一份安全代码指南文档,旨在用作我们代码审查的一部分。首先让我印象深刻的是一个项目,上面写着“不要使用内部类”。我认为这似乎是一个非常严厉和笼统的声明。如果使用正确,内部类很好吗?但我做了一些谷歌搜索,发现了这个,为了方便起见,在这里引用。

规则 5:不要使用内部类

一些 Java 语言书籍说内部类只能由包含它们的外部类访问。这不是真的。Java 字节码没有内部类的概念,因此内部类被编译器翻译成普通类,而这些普通类恰好可以被同一个包中的任何代码访问。规则 4 说不依赖包范围进行保护。

但是等等,情况会变得更糟。内部类可以访问封闭外部类的字段,即使这些字段被声明为私有。并且内部类被翻译成一个单独的类。为了允许这个单独的类访问外部类的字段,编译器默默地将这些字段从私有更改为包范围!暴露内部类已经够糟糕了,但更糟糕的是编译器默默地否决了您将某些字段设为私有的决定。如果可以,请不要使用内部类。(具有讽刺意味的是,新的 Java 2 doPrivileged() API 使用指南建议您使用内部类来编写特权代码。这就是我们不喜欢 doPrivileged() API 的原因之一。)

我的问题是

  1. 这种行为在 java 5 / 6 中是否仍然存在?
  2. 考虑到除了外部类和内部类之外的任何试图访问外部类的私有成员的类都不会编译,这实际上是一种安全风险吗?
  3. 它是否构成足够的安全风险来警告“准则”“不要使用内部类”?
4

10 回答 10

19

这些信息已经过时了大约十年。匿名内部类的广泛使用AccessController.doPrivileged应该是一个线索。(如果您不喜欢该 API,请考虑 JDK 中错误缺失的try-finally块的比例。)

策略是,如果两个类由不同的类加载器加载或具有不同的证书,则它们不能共享同一个包。为了获得更多保护,请在您的罐子清单中将包裹标记为已密封。因此,从安全的角度来看,“规则 4”是虚假的,因此也是这条规则。

在任何情况下,制定安全策略您都应该了解您要防范的内容。这些类型的策略用于处理可能具有不同信任级别的移动代码(移动的代码)。除非您正在处理移动代码,或者您的代码要进入可能需要的库,否则这些预防措施几乎没有意义。然而,使用健壮的编程风格几乎总是一个好主意,例如复制和验证参数和返回值。

于 2009-12-20T11:12:28.623 回答
10

这种行为在 java 5 / 6 中是否仍然存在?

与描述的不完全一样;我从未见过这样的编译器:

为了允许这个单独的类访问外部类的字段,编译器默默地将这些字段从私有更改为包范围!

相反,IIRC Sun Java 3/4 创建了一个访问器,而不是修改该字段。

Sun Java 6 (javac 1.6.0_16 ) 创建一个静态访问器:

public class InnerExample {
    private int field = 42; 

    private class InnerClass {
        public int getField () { return field; };
    }

    private InnerClass getInner () { 
        return new InnerClass();
    }

    public static void main (String...args) {
        System.out.println(new InnerExample().getInner().getField());
    }
}


$ javap -classpath bin -private InnerExample
Compiled from "InnerExample.java"
public class InnerExample extends java.lang.Object{
    private int field;
    public InnerExample();
    private InnerExample$InnerClass getInner();
    public static void main(java.lang.String[]);
    static int access$000(InnerExample);
}


$ javap -classpath bin -c -private InnerExample
static int access$000(InnerExample);
  Code:
   0:   aload_0
   1:   getfield    #1; //Field field:I
   4:   ireturn

考虑到除了外部类和内部类之外的任何试图访问外部类的私有成员的类都不会编译,这实际上是一种安全风险吗?

我在这里推测了一下,但是如果您针对类进行编译,则不会,但是如果添加了,access$000则可以编译使用访问器的代码。

import java.lang.reflect.*;

public class InnerThief {
    public static void main (String...args) throws Exception {
        for (Method me : InnerExample.class.getDeclaredMethods()){
            System.out.println(me);
            System.out.printf("%08x\n",me.getModifiers());
        }

        System.out.println(InnerExample.access$000(new InnerExample()));
    }
}

有趣的是,合成访问器有修饰符标志00001008,如果你添加一个包级别的静态方法,它有标志00000008。JVM 规范的第二版中没有该标志值,但它似乎阻止了 javac 看到该方法。

所以看起来那里有一些安全功能,但我找不到任何文档。

(因此这篇文章在 CW 中,以防有人知道 0x1000 在类文件中的含义)

于 2009-12-20T11:40:08.480 回答
9
  1. 是的,这种行为仍然存在。
  2. 这是一个安全风险,因为流氓类可以用标准 javac 以外的其他东西制作。
  3. 这取决于你有多少偏执:) 如果你不允许外星类在你的 JVM 中运行,我看不出有什么问题。如果你这样做了,你会遇到更大的问题(沙箱和所有)
  4. 我知道你只有 3 个问题,但和这里的其他人一样,我认为这是一个愚蠢的限制。
于 2009-12-20T10:36:30.090 回答
6

这种行为在 java 5 / 6 中是否仍然存在?

您可以使用javap工具来确定您的二进制文件正在公开什么以及如何公开。

package demo;
public class SyntheticAccessors {
  private boolean encapsulatedThing;

  class Inner {
    void doSomething() {
      encapsulatedThing = true;
    }
  }
}

上面的代码(用 Sun Java 6 编译javac)在以下位置创建了这些方法SyntheticAccessors.class

Compiled from "SyntheticAccessors.java"
public class demo.SyntheticAccessors extends java.lang.Object{
    public demo.SyntheticAccessors();
    static void access$0(demo.SyntheticAccessors, boolean);
}

注意新access$0方法。

于 2009-12-20T11:23:49.447 回答
4

这种代码安全性的想法有点愚蠢。如果您想要代码级别的安全性,请使用混淆工具。就像@skaffman 在上面的评论中所说,“代码可见性从来都不是安全功能。即使是私有成员也可以使用反射访问。”。

如果您正在分发编译后的代码而不是对其进行混淆,那么如果您担心人们会修改您的私人信息,那么使用内部类是您最后的担心。

如果您正在托管您的代码,那么您为什么担心有人在您的内部类周围戳?

如果您要链接一些您不信任且无法在运行时检查的第 3 方代码,则将其沙箱化。

就像我上面说的,如果这真的是贵公司的政策,请及时向thedailywtf.com报告贵公司

于 2009-12-20T10:47:15.087 回答
4

您应该考虑您的应用程序必须提供什么样的安全性。具有安全架构的应用程序不会遇到这些命名问题。

如果不允许用户使用您的代码做某事,您必须分离此功能并在服务器上运行它(用户无权访问类文件)。

请记住,您始终可以反编译 java 类文件。并且不要依赖“默默无闻的安全”。即使是混淆的代码也可以被分析、理解和修改。

于 2009-12-20T10:54:27.823 回答
4

恶意代码可以使用 Java 反射来获取 JVM 中的任何信息,除非有安全管理器禁止这样做,这包括将私有字段更改为公共字段等等。

我个人的观点是,不这样做的原因被其他可能性压倒了,所以如果你需要它,它是有意义的,并且它是可读的,使用内部类。

于 2009-12-20T10:55:59.373 回答
3

“考虑到除了外部类和内部类之外,任何试图访问外部类的私有成员的类都不会编译,这实际上是一种安全风险吗?”

即使在正常情况下无法编译,您仍然可以生成自己的字节码。但这不是避免内部类的理由。您所要做的就是假设您所有的内部类都是公共的。

如果您真的希望能够运行不受信任的代码,请了解如何使用Java 安全架构设置您自己的沙箱和安全级别,这并不难。但大多数情况下,您应该避免在安全的环境中运行随机代码。

于 2009-12-20T12:51:03.657 回答
-1

请注意,列出的缺点不适用于static内部类,因为它们没有对其封闭类(或真正的对象)的隐式访问。

因此,如果这条规则对您的公司有所帮助,那么将静态内部类排除在外可能是一个想法,因为它们提供了一种在许多情况下很有用的封装方式。

@Tom,引用Java 语言规范,“成员类可能是静态的,在这种情况下,它们无法访问周围类的实例变量”

于 2009-12-20T11:07:53.263 回答
-1

废话!遵循相同的逻辑,也不要编写公共方法,因为它们可以访问私有字段,gush!

于 2009-12-20T17:59:26.010 回答