如何将setAccessible限制为仅“合法”使用?


100

我对的力量了解java.lang.reflect.AccessibleObject.setAccessible得越多,我对它能做的事情就越惊讶。这是根据我对问题的回答(使用反射更改静态最终File.separatorChar用于单元测试)改编而成的。

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

您可以做真正令人发指的事情:

public class UltimateAnswerToEverything {
   static Integer[] ultimateAnswer() {
      Integer[] ret = new Integer[256];
      java.util.Arrays.fill(ret, 42);
      return ret;
   }   
   public static void main(String args[]) throws Exception {
      EverythingIsTrue.setFinalStatic(
         Class.forName("java.lang.Integer$IntegerCache")
            .getDeclaredField("cache"),
         ultimateAnswer()
      );
      System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42"
   }
}

大概是API设计人员意识到了可滥用性setAccessible,但是必须承认它具有合法的用途来提供它。所以我的问题是:

  • 真正合法的用途是setAccessible什么?
    • Java是否可以被设计为一开始就没有这种需求?
    • 这种设计的负面后果(如果有)是什么?
  • setAccessible只能限制合法使用吗?
    • 只有通过SecurityManager吗?
      • 它是如何工作的?白名单/黑名单,粒度等?
      • 必须在您的应用程序中对其进行配置是否常见?
    • setAccessible无论SecurityManager配置如何,我都可以将自己的类写成-proof 吗?
      • 还是由谁来管理配置?

我猜一个更重要的问题是:我需要为此担心吗???

我的课程都没有任何类似的可执行隐私。现在无法执行单例模式(除了对其优点的怀疑)。正如我上面的摘录所显示的,甚至关于Java基础如何工作的一些基本假设甚至都无法保证。

这些问题不是真的吗???


好的,我刚刚确认:感谢setAccessible,Java字符串不是不可变的。

import java.lang.reflect.*;

public class MutableStrings {
   static void mutate(String s) throws Exception {
      Field value = String.class.getDeclaredField("value");
      value.setAccessible(true);
      value.set(s, s.toUpperCase().toCharArray());
   }   
   public static void main(String args[]) throws Exception {
      final String s = "Hello world!";
      System.out.println(s); // "Hello world!"
      mutate(s);
      System.out.println(s); // "HELLO WORLD!"
   }
}

我是唯一认为这是一个巨大问题的人吗?


30
您应该看到他们在C ++中可以做什么!(哦,可能还有C#。)
Tom Hawtin-抢险

Answers:


105

我需要为此担心吗???

这完全取决于您正在编写哪种类型的程序以及哪种体系结构。

如果您要向世界各地的人们分发名为foo.jar的软件组件,那么您完全会受到他们的摆布。他们可以修改.jar中的类定义(通过逆向工程或直接字节码操作)。他们可以在自己的JVM中运行您的代码,等等。在这种情况下,担心将无济于事。

如果您正在编写仅通过HTTP与人和系统交互的Web应用程序,并且可以控制应用程序服务器,那么也不必担心。确保您公司的其他编码人员可以创建破坏您单例模式的代码,但前提是他们确实愿意。

如果您将来的工作是在Sun Microsystems / Oracle上编写代码,而您的任务是为Java核心或其他受信任的组件编写代码,那么您应该意识到这一点。但是,担心只会让你掉头发。无论如何,它们可能会让您阅读《安全编码指南》以及内部文档。

如果您要编写Java applet,则应注意安全框架。您会发现尝试调用setAccessible的未签名小程序只会导致SecurityException。

setAccessible并不是围绕常规完整性检查的唯一方法。有一个名为sun.misc.Unsafe的非API核心Java类,它可以做几乎所有它想做的事情,包括直接访问内存。本机代码(JNI)也可以绕过这种控制。

在沙盒环境(例如Java Applets,JavaFX)中,每个类都有一组权限,对不安全,setAccessible的访问以及定义的本机实现均由SecurityManager控制。

“ Java访问修饰符并非旨在成为一种安全机制。”

这很大程度上取决于Java代码的运行位置。核心Java类确实使用访问修饰符作为强制执行沙箱的安全机制。

setAccessible的真正合法用途是什么?

Java核心类使用它作为一种简单的方法来访问出于安全原因而必须保持私有的内容。作为示例,Java序列化框架在反序列化对象时使用它来调用私有对象构造函数。有人提到System.setErr,这将是一个很好的例子,但是奇怪的是System类方法setOut / setErr / setIn都使用本机代码来设置final字段的值。

另一个明显的合法用途是需要窥探对象内部的框架(持久性,Web框架,注入)。

在我看来,调试器不属于此类,因为它们通常不在同一JVM进程中运行,而是使用其他方式(JPDA)与JVM进行接口。

Java是否可以被设计为一开始就没有这种需求?

要回答这个问题,这是一个很深的问题。我想是的,但是您需要添加一些其他可能并不理想的机制。

您可以将setAccessible限制为仅合法使用吗?

您可以应用的最简单的OOTB限制是拥有SecurityManager并只允许setAccessible来访问来自某些来源的代码。这已经是Java所做的-允许来自JAVA_HOME的标准Java类执行setAccessible,而不允许来自foo.com的未签名的applet类进行setAccessible。如前所述,这种许可是二进制的,就某种意义上来说,无论该许可与否。没有明显的方法允许setAccessible修改某些字段/方法而不允许其他字段/方法。但是,使用SecurityManager可以禁止类完全引用某些包,无论有无反射。

无论SecurityManager的配置如何,我都可以编写可设置setAccessible-proof的类吗?...还是由谁来管理配置?

您不能,而且您当然可以。


“如果您要向全世界的人们分发名为foo.jar的软件组件,无论如何您完全是他们的摆布。他们可以通过反向工程或直接字节码操作来修改.jar中的类定义。他们可以在自己的JVM中运行您的代码,等等。在这种情况下,担心将对您没有好处。” 还是这样吗?关于提高软件篡改难度的任何建议(希望如此)?因此,很难将Java桌面应用程序扔出窗口。
Sero

@naaz我必须说什么都没有改变。只是架构正在对您不利。我可以完全控制我机器上运行的任何软件。最强的威慑力可能是合法的,但我并不认为这对所有情况都适用。我认为Java二进制文件最容易逆转,但是有经验的人会篡改任何东西。混淆使难以进行大的更改会有所帮助,但是任何类型的布尔值(例如版权检查)仍然可以忽略。您可以尝试将关键部件放在服务器上。
萨米·科伊夫

denuvo怎么样?
Sero

15
  • 真正合法的用途是setAccessible什么?

单元测试,JVM内部(例如,实现System.setError(...))等等。

  • Java是否可以被设计为一开始就没有这种需求?
  • 这种设计的负面后果(如果有)是什么?

许多事情将无法实现。例如,各种Java持久性,序列化和依赖项注入都依赖于反射。几乎所有在运行时都依赖JavaBeans约定的东西。

  • setAccessible只能限制合法使用吗?
  • 只有通过SecurityManager吗?

是。

  • 它是如何工作的?白名单/黑名单,粒度等?

这取决于权限,但我相信使用权限setAccessible是二进制的。如果需要粒度,则需要对要限制的类使用其他类加载器和其他安全管理器。我猜您可以实现一个自定义安全管理器,该管理器可以实现更细粒度的逻辑。

  • 必须在您的应用程序中对其进行配置是否常见?

没有。

  • setAccessible无论SecurityManager配置如何,我都可以将自己的类写成-proof 吗?
    • 还是由谁来管理配置?

不,你不能,是的。

另一种选择是通过源代码分析工具“强制”执行此操作。例如风俗pmdfindbugs规则。或对(say)标识的代码进行选择性代码审查grep setAccessible ...

作为对后续行动的回应

我的课程都没有任何类似的可执行隐私。现在无法执行单例模式(除了对其优点的怀疑)。

如果那让您担心,那么我想您需要担心。但是实际上,您不应该试图迫使其他程序员尊重您的设计决策。如果人们愚蠢到可以使用反射无意中创建多个单例实例(例如),那么他们可以承受后果。

另一方面,如果您的意思是“隐私”包含保护敏感信息不被泄露的含义,那么您就是在树错误的树。在Java应用程序中保护敏感数据的方法是不允许将不受信任的代码放入处理敏感数据的安全沙箱中。Java访问修饰符并非旨在成为一种安全机制。

<字符串示例>-我是唯一认为这是一个巨大问题的人吗?

可能不是唯一的一个:-)。但是海事组织,这不是一个问题。公认的事实是,不应在沙箱中执行不受信任的代码。如果您拥有受信任的代码/受信任的程序员从事此类工作,那么您的问题会比意外地可变的字符串更糟糕。(想想逻辑炸弹,通过秘密渠道泄露数据等)

在您的开发或运营团队中,有一些方法可以解决(或减轻)“不良行为者”的问题。但是它们是昂贵且受限的...对于大多数用例而言,它们是过大的。


1
对于诸如持久性实现之类的事情也很有用。反射对于调试器并没有真正的用处(尽管它原本打算这样做)。您不能SecurityManager为此而有效地更改(子类),因为您得到的只是权限检查-您真正可以做的就是查看acc并可能在栈中检查当前线程)。grep java.lang.reflect.
Tom Hawtin-抢险

@Stephen C:已经+1了,但是我也很感谢您在我刚刚添加的另一个问题上的投入。提前致谢。
polygenelubricants 2010年

请注意,grep是一个可怕的想法。在Java中动态执行代码的方法有很多,您几乎肯定会错过一种方法。通常,将代码列入黑名单是失败的秘诀。

“ Java访问修饰符并非旨在成为一种安全机制。” -实际上,是的。Java旨在允许可信和不可信代码共存于同一虚拟机中-这从一开始就是其核心要求的一部分。但仅当系统使用锁定的SecurityManager运行时。沙盒代码的能力完全取决于访问说明符的执行。
Jules

@Jules-大多数Java专家不同意这一点;如stackoverflow.com/questions/9201603/...
斯蒂芬ç

11

在这种情况下,反思确实与安全/保障正交。

我们如何限制反射?

Java具有安全管理器,并且ClassLoader是其安全模型的基础。就您而言,我想您需要看看java.lang.reflect.ReflectPermission

但这并不能完全解决反思问题。可用的反射功能应遵循细粒度的授权方案,而现在情况并非如此。例如,允许某些框架使用反射(例如Hibernate),但不使用其余代码。或者为了调试目的而允许程序仅以只读方式反映。

将来可能成为主流的一种方法是使用所谓的镜子将反射功能与类分开。请参阅《镜像:元级设施的设计原则》。但是,还有其他各种研究可以解决这个问题。但是我同意动态语言的问题比静态语言更为严重。

我们应该担心反射给我们带来的超级大国吗?是的,没有。

的,因为Java平台应该由Classloader安全管理器保护。反射的混乱能力可以看作是一种突破。

没有在这个意义上,大多数系统都是反正不是完全安全的。很多类经常可以被子类化,您可能已经就这样滥用了系统。当然类可以制成final密封,使他们不能在其他罐子被继承。但是据此,只有少数几个类得到正确保护(例如String)。

有关最终课程的详细信息,请参见此答案。另请参阅Sami Koivu的博客,以获取有关安全性的更多Java 技巧

Java的安全性模型在某些方面可以视为不足。诸如NewSpeak之类的某些语言甚至采用了更为激进的模块化方法,在该方法中,您只能访问依赖关系反转显式提供给您的内容(默认情况下为无)。

同样重要的是要注意,安全性始终是相对的。在语言级别,例如,您不能阻止模块形式消耗100%的CPU或消耗所有内存,直到一个OutOfMemoryException。这些问题需要通过其他方式解决。将来我们可能会看到Java扩展了资源利用率配额,但是明天就不行了:)

我可以在这个问题上做更多的扩展,但是我认为我已经表明了自己的意思。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.