Java使得在运行时编译Java代码成为可能…任何Java代码!
编译的入口点是ToolProvider
类。它的Javadoc如下:
Provides methods for locating tool providers, for example, providers of compilers. This class complements the functionality of ServiceLoader.
翻译一下就是,提供用于定位工具提供者(例如,编译器的提供者)的方法。此类补充了ServiceLoader的功能。
从10年前发布的1.6版开始,此类就可以在Java中使用,但似乎已被很大程度上忽略了。
代码
示例代码片段:
public class EvilExecutor {
private String readCode(String sourcePath) throws FileNotFoundException {
InputStream stream = new FileInputStream(sourcePath);
String separator = System.getProperty("line.separator");
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
return reader.lines().collect(Collectors.joining(separator));
}
private Path saveSource(String source) throws IOException {
String tmpProperty = System.getProperty("java.io.tmpdir");
Path sourcePath = Paths.get(tmpProperty, "Harmless.java");
Files.write(sourcePath, source.getBytes(UTF_8));
return sourcePath;
}
private Path compileSource(Path javaFile) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, javaFile.toFile().getAbsolutePath());
return javaFile.getParent().resolve("Harmless.class");
}
private void runClass(Path javaClass)
throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
URL classUrl = javaClass.getParent().toFile().toURI().toURL();
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{classUrl});
Class<?> clazz = Class.forName("Harmless", true, classLoader);
clazz.newInstance();
}
public void doEvil(String sourcePath) throws Exception {
String source = readCode(sourcePath);
Path javaFile = saveSource(source);
Path classFile = compileSource(javaFile);
runClass(classFile);
}
public static void main(String... args) throws Exception {
new EvilExecutor().doEvil(args[0]);
}
}
解释:
- readCode():从文件系统上的任意文件读取源代码,并将其作为字符串返回。另一种实现方式是从整个网络获取源。
- saveSource():根据源代码在已启用读取的目录中创建一个新文件。该文件名是硬编码的,更完善的版本将解析code参数以创建一个根据其包含的类名命名的文件。
- compileSource():从Java文件中编译类文件。
- runClass:加载已编译的类并实例化一个新对象。为了独立于任何强制类型转换,应在外部源代码类的构造函数中设置要执行的代码。
问题
从功能的角度来看,与不提供此功能的其他语言相比,即时编译代码可提高Java语言的价值。从安全角度来看,这是一场噩梦! 能够在生产中执行任意代码的想法应该使任何IT组织(包括开发人员)(如果不是大多数)中的任何人都感到不寒而栗。
经验丰富的开发人员/操作人员或普通读者可能还记得Java安全管理器以及如何激活它:
java -Djava.security.manager -cp target/classes ch.frankel.blog.runtimecompile.EvilExecutor harmless.txt
执行上述命令行将产生以下结果:
java.lang.SecurityManager@4e25154f
Exception in thread "main" java.security.AccessControlException:
access denied ("java.io.FilePermission" "harmless.txt" "read")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.SecurityManager.checkRead(SecurityManager.java:888)
at java.io.FileInputStream.<init>(FileInputStream.java:127)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at ch.frankel.blog.runtimecompile.EvilExecutor.readCode(EvilExecutor.java:19)
at ch.frankel.blog.runtimecompile.EvilExecutor.doEvil(EvilExecutor.java:47)
at ch.frankel.blog.runtimecompile.EvilExecutor.main(EvilExecutor.java:56)
结论
JVM提供了很多功能。与任何工具一起使用时,无论好坏,它们都可以使用。每个人都有责任确保自己的JVM的安全,在敏感领域——银行业、军事等领域,情况更是如此。