最近,我项目的安全团队发布了一个安全的代码指南文档,旨在用作我们的代码审查的一部分。让我印象深刻的第一件事是“不要使用内部课程”。我认为这似乎是一个非常沉重和彻底的声明。如果正确使用,内部类很好吗?但我做了一些谷歌搜索并找到了this,为方便起见引用了这里。
规则5:不要使用内部类
一些Java语言书籍说 内部类只能通过访问 包含它们的外部类。 这不是真的。 Java字节码有 没有内在阶级的概念,所以内在 类由编译器翻译 进入普通班级 可以访问相同的任何代码 包。规则4说不依赖 关于包装保护范围。
但等等,情况变得更糟。内心的 class可以访问。的字段 封闭外类,即使这些 字段被声明为私有。而且 内部类被翻译成一个 单独的课程。为了这个 单独的类访问字段 外部类,编译器默默地 将这些字段从私有更改为 包装范围!这太糟糕了 内在的暴露,但它是 更糟糕的是编译器 默默地推翻你的决定 使一些领域私有化。不要用 内部课程,如果你能帮助它。 (具有讽刺意味的是,新的Java 2 doPrivileged()API使用指南 建议你使用内部类 写特权代码。那是一个 我们不喜欢的原因 doPrivileged()API。)
我的问题是
答案 0 :(得分:18)
此信息已过时十年左右。匿名内部类与AccessController.doPrivileged
的广泛使用应该是一个线索。 (如果您不喜欢API,请考虑JDK中错误丢失的try
- finally
块的比例。)
策略是如果两个类由不同的类加载器加载或具有不同的证书,则没有两个类可以共享相同的包。为了获得更多保护,请将包裹密封在罐子的清单中。因此,从安全的角度来看,“规则4”是虚假的,因此也是这个规则。
在任何情况下,制定安全策略都应该了解您所保护的内容。这些策略用于处理可能具有不同信任级别的移动代码(移动的代码)。除非您正在处理移动代码,或者您的代码进入了可能需要的库,否则这些预防措施中没有什么意义。但是,使用健壮的编程风格几乎总是一个好主意,例如复制和验证参数和返回值。
答案 1 :(得分:9)
答案 2 :(得分:9)
这种行为在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在类文件中的含义)
答案 3 :(得分: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
方法。
答案 4 :(得分:4)
代码中这种安全性的想法有点愚蠢。如果您需要代码级安全性,请使用混淆工具。就像@skaffman在上面的评论中所说的那样,“代码可见性从来就不是一个安全功能。甚至可以使用反射来访问私有成员。”。
如果您正在分发已编译的代码而不对其进行模糊处理,那么如果您担心人们修改您的私有部分,那么使用内部课程是您最后的担忧。
如果您正在托管您的代码,那么您为什么担心有人会在您的内部课程中徘徊?
如果您要链接某些您不信任且无法在运行时检查的第三方代码,请将其沙箱化。
就像我上面所说,如果这是贵公司的政策,请及时向贵公司报告thedailywtf.com
答案 5 :(得分:4)
您应该考虑应用程序必须提供哪种安全性。具有安全体系结构的应用程序不会遇到这些命名问题。
如果某些用户不允许使用您的代码,则必须单独使用此功能并在服务器上运行(用户无法访问类文件)。
请记住,您始终可以反编译java类文件。并且不要依赖“默默无闻的安全”。甚至可以分析,理解和修改混淆代码。
答案 6 :(得分:4)
恶意代码可以使用java反射来获取JVM中的任何信息,除非有安全管理器禁止此操作,这包括将私有字段更改为公共等等。
我的个人意见是,不被其他可能性所淹没的原因,所以如果你需要它,它是有意义的,它是可读的,使用内部类。
答案 7 :(得分:3)
“这实际上是一个安全风险,因为除了外部类和内部类之外,任何试图访问外部类的私有成员的类都不会编译?”
即使在正常情况下无法编译,您仍然可以生成自己的字节码。但这没有理由避免内部阶级。您所要做的就是假设您的所有内部类都是公开的。
如果您真的希望能够运行不受信任的代码,请了解如何使用The Java Security Architecture设置自己的沙箱和安全级别,这并不难。但大多数情况下,您应避免在安全环境中运行随机代码。
答案 8 :(得分:-1)
请注意,列出的缺点不适用于static
内部类,因为它们没有隐式访问其封闭类(或实际对象)。
因此,如果这条规则可以帮助您的公司,那么可能会想到获得静态内部类,因为它们提供了一种封装方式,这在很多情况下很有用。
@Tom,引用Java language specification,“会员类可能静态,在这种情况下,他们对周围班级的实例变量无法访问 “
答案 9 :(得分:-1)
无义!遵循相同的逻辑,也不要写公共方法,因为他们可以访问私有字段,gush!