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

2022-08-31 10:20:46

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

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
      • 它是如何工作的?白名单/黑名单、粒度等?
      • 是否经常必须在应用程序中对其进行配置?
    • 我可以将我的类编写为 -proof,而不考虑配置吗?setAccessibleSecurityManager
      • 还是受制于管理配置的人?

我想还有一个重要的问题是:我需要担心这个吗???

我的课程中没有一个有任何可强制执行的隐私表象。单例模式(将对其优点的怀疑放在一边)现在不可能执行。正如我上面的片段所显示的,即使是一些关于Java基础如何工作的基本假设,也远未得到保证。

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


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

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!"
   }
}

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


答案 1

我需要担心吗???

这完全取决于你正在编写的程序类型以及哪种架构。

如果你正在向世界人民分发一个名为foo.jar的软件组件,那么无论如何,你完全受他们的摆布。他们可以修改.jar中的类定义(通过逆向工程或直接字节码操作)。他们可以在自己的JVM中运行您的代码,等等。在这种情况下,担心对你没有好处。

如果你正在编写一个Web应用程序,它只通过HTTP与人和系统接口,并且你控制着应用程序服务器,那么这也不是一个问题。当然,您公司的程序员同事可能会创建破坏您的单例模式的代码,但前提是他们真的想这样做。

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

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

setAccessible 并不是唯一绕过传统完整性检查的东西。有一个非API的核心Java类,叫做sun.misc.Unsafe,它几乎可以做任何它想做的事情,包括直接访问内存。本机代码 (JNI) 也可以绕过这种控件。

在沙盒环境(例如 Java 小程序、JavaFX)中,每个类都有一组权限和对 Unsafe 的访问权限,setAccessible 和定义本机实现由 SecurityManager 控制。

“Java访问修饰符并不是一种安全机制。

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

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

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

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

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

Java可以被设计成一开始就没有这种需求吗?

这是一个非常深刻的问题,需要很好地回答。我想是的,但是您需要添加一些其他机制,这些机制可能不是那么可取。

是否可以将 setAccessible 限制为仅供合法使用?

您可以应用的最直接的 OOTB 限制是使用安全管理器,并允许 setAccessible 仅对来自某些来源的代码进行访问。这就是Java已经做的事情 - 来自JAVA_HOME的标准Java类被允许做setAccessible,而来自 foo.com 的未签名的小程序类不允许做setAccessible。如前所述,这种权限是二元的,从某种意义上说,人们要么拥有它,要么没有。没有明显的方法允许 setAccessible 修改某些字段/方法,同时禁止其他字段/方法。但是,使用 SecurityManager,您可以禁止类完全引用某些包,无论是否具有反射。

我可以将我的类设置为设置为可验证的,而不管 SecurityManager 配置如何?...还是受制于管理配置的人?

你不能,你肯定是。


答案 2
  • 什么是真正合法的用途?setAccessible

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

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

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

  • 您能限制为仅合法使用吗?setAccessible
  • 它只是通过吗?SecurityManager

是的。

我并不是说通过黑名单/白名单这样做是一个好主意。我是说这是(AFAIK)做到这一点的唯一方法。SecurityManager

另外,请注意,Java 17标记为已弃用;用于删除。请参阅 JEP 411。这是另一个坏主意的原因。SecurityManager

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

这取决于权限,但我相信使用的权限是二进制的。如果需要粒度,则需要对要限制的类使用具有不同安全管理器的不同类装入器。我想你可以实现一个自定义安全管理器,实现更细粒度的逻辑。setAccessible

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

不。

  • 我可以将我的类编写为 -proof,而不考虑配置吗?setAccessibleSecurityManager
  • 还是受制于管理配置的人?

不,你不能,是的,你是。

另一种选择是通过源代码分析工具“强制”这一点;例如,自定义或规则。或者对由(比如)标识的代码进行选择性代码审查。pmdfindbugsgrep setAccessible ...

对后续工作的回应

我的课程中没有一个有任何可强制执行的隐私表象。单例模式(将对其优点的怀疑放在一边)现在不可能执行。

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

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

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

可能不是一的一个:-)。但IMO,这不是一个问题。不受信任的代码应在沙箱中执行,这是公认的事实。如果你有受信任的代码/受信任的程序员做这样的事情,那么你的问题比意外的可变字符串更糟糕。(想想逻辑炸弹,通过隐蔽渠道泄露数据等)

有一些方法可以处理(或缓解)开发或运营团队中的“不良行为者”问题。但它们成本高昂且限制性强...对于大多数用例来说,这是过度的。