我越了解java.lang.reflect.AccessibleObject.setAccessible
的力量,我就越能对它做什么感到惊讶。这是根据我对问题(Using reflection to change static final File.separatorChar for unit testing)的回答改编的。
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
的真正合法用途是什么?
setAccessible
仅限于合法用途?
SecurityManager
吗?
setAccessible
- 无论SecurityManager
配置如何都能证明?
我想一个更重要的问题是:我是否需要担心这个?
我的课程都没有任何类似的可强制执行的隐私。单身模式(对其优点置之不理)现在无法实施。正如我上面的代码片段所示,甚至关于Java基础工作原理的一些基本假设甚至都不能保证。
这些问题不是真的吗?
好的,我刚刚确认:感谢setAccessible
,Java字符串 NOT 不可变。
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!"
}
}
我是唯一一个认为这是一个巨大问题的人吗?
答案 0 :(得分:103)
我是否需要担心这个???
这完全取决于您正在编写的程序类型以及何种体系结构。
如果您正在向全世界的人们分发一个名为foo.jar的软件组件,那么无论如何,您完全都会受到他们的怜悯。他们可以修改.jar中的类定义(通过逆向工程或直接字节码操作)。他们可以在自己的JVM等中运行你的代码。在这种情况下,担心对你没有好处。
如果您正在编写一个仅通过HTTP与人员和系统连接并且您控制应用程序服务器的Web应用程序,那么它也不是一个问题。当然,贵公司的编码人员可能会创建破坏单身模式的代码,但前提是他们真的想这样做。
如果您未来的工作是在Sun Microsystems / Oracle上编写代码,并且您的任务是为Java核心或其他可信组件编写代码,那么您应该注意这一点。然而,担心会让你失去头发。无论如何,他们可能会让你阅读Secure Coding Guidelines以及内部文档。
如果您要编写Java applet,那么您应该注意安全框架。您会发现尝试调用setAccessible的未签名小程序只会导致SecurityException。
setAccessible并不是传统完整性检查的唯一方法。有一个名为sun.misc.Unsafe的非API核心Java类可以完成任何想做的事情,包括直接访问内存。本机代码(JNI)也可以绕过这种控制。
在沙盒环境中(例如Java Applets,JavaFX),每个类都有一组权限,对Unsafe,setAccessible和定义本机实现的访问权限由SecurityManager控制。
“Java访问修饰符不是一种安全机制。”
这在很大程度上取决于Java代码的运行位置。核心Java类确实使用访问修饰符作为安全机制来强制执行沙箱。
setAccessible的真正合法用途是什么?
Java核心类使用它作为一种简单的方法来访问因安全原因必须保持私有的内容。例如,Java Serialization框架在反序列化对象时使用它来调用私有对象构造函数。有人提到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吗? ......或者我是否受到管理配置的人的支配?
你不可能而且你肯定是。
答案 1 :(得分:14)
setAccessible
的真正合法用途是什么?
单元测试,JVM的内部(例如实现System.setError(...)
)等。
- Java的设计是否首先没有这个需求?
- 这种设计的负面后果(如果有的话)是什么?
许多事情都是无法实现的。例如,各种Java持久性,序列化和依赖性注入都依赖于反射。几乎任何在运行时都依赖于JavaBeans约定的东西。
- 您是否可以将
setAccessible
仅限于合法用途?- 仅通过
SecurityManager
吗?
是
- 它是如何工作的?白名单/黑名单,粒度等?
这取决于权限,但我相信使用setAccessible
的权限是二进制的。如果需要粒度,则需要为要限制的类使用不同的类加载器和不同的安全管理器。我想你可以实现一个实现更细粒度逻辑的自定义安全管理器。
- 在您的应用程序中配置它是否很常见?
没有
- 我可以将我的课程编写为
setAccessible
- 无论SecurityManager
配置如何都能证明?
- 或者我受管谁配置的左右?
不,你不能,是的,你是。
另一种选择是通过源代码分析工具“强制执行”;例如自定义pmd
或findbugs
规则。或者由(例如)grep setAccessible ...
确定的代码的选择性代码审查。
回应后续行动
我的课程都没有任何类似的可强制执行的隐私。单身模式(对其优点置之不理)现在无法实施。
如果这让你担心,那么我想你需要担心。但实际上你不应该尝试强迫其他程序员尊重你的设计决策。如果他们愚蠢到无偿地创建你的单身人士的多个实例(例如),他们就可以忍受后果。
如果你的意思是“隐私”包含保护敏感信息不被泄露的含义,你也会咆哮错误的树。保护敏感数据的方法不是允许不受信任的代码进入处理敏感数据的安全沙箱。 Java访问修饰符不是一种安全机制。
< String example> - 我是唯一一个认为这是一个巨大问题的人吗?
可能不是唯一一个:-)。但IMO,这不是一个问题。事实上,不受信任的代码应该在沙箱中执行。如果你有可靠的代码/一个受信任的程序员做这样的事情,那么你的问题更糟糕的是有意想不到的可变字符串。 (想想逻辑炸弹......)
答案 2 :(得分:11)
在这种观点下,反思确实与安全/保障正交。
我们如何限制反思?
Java将安全管理器和ClassLoader
作为其安全模型的基础。在你的情况下,我想你需要看看java.lang.reflect.ReflectPermission
。
但这并不能完全解决反思问题。可用的反射功能应遵循细粒度的授权方案,而现在并非如此。例如。允许某些框架使用反射(例如Hibernate),但不使用其余的代码。或者允许程序仅以只读方式反映,以进行调试。
未来可能成为主流的一种方法是使用所谓的镜像来将反射功能与类分开。见Mirrors: Design Principles for Meta-level Facilities。然而,有许多other research可以解决这个问题。但我同意动态语言的问题比静态语言更严重。
我们应该担心反射给我们的超级大国吗?是和否。
是,因为Java平台应该由Classloader
和安全管理器保护。混乱反射的能力可以被视为违规行为。
否在某种意义上说大多数系统都不完全安全。许多类经常被子类化,你可能已经滥用了系统。当然可以将类设为final
或sealed,以便它们不能在其他jar中进行子类化。但根据这一点,只有少数类被正确保护(例如String)。
请参阅此answer about final class以获得更好的解释。另请参阅Sami Koivu中的博客,了解有关安全性的更多Java黑客攻击。
Java的安全模型在某些方面可能被视为不足。某些语言(如NewSpeak)采用了更为激进的模块化方法,在这种方法中,您只能访问通过依赖性反转明确赋予您的内容(默认情况下没有任何内容)。
同样重要的是要注意安全性无论如何 relative 。在语言级别,您可以例如不阻止模块表单消耗100%的CPU或消耗最多OutOfMemoryException
的所有内存。需要通过其他方式解决这些问题。我们可能会在未来看到Java扩展了资源利用配额,但不是明天:)
我可以在这个问题上进一步扩展,但我认为我已经说明了这一点。