一种确保Java程序安全的简单方式
SecurityManager介绍
Java序列化漏洞利用中突出的一点是,一旦一个服务器端的Java应用程序被破解,那么下一步就是获取主机上的shell访问权限,这就是我们所熟知的远程代码执行(RCE)。
然而有趣的是,其实从Java 1.1开始,Java中就存在一种方式来限制代码执行,并能够防止远程代码执行,这就是SecurityManager。在启用SecurityManager之后,Java代码会在一个非常安全的沙盒中执行,而这能够防止远程代码执行。
java -Djava.security.manager com.example.Hello
这会以$JAVA_HOME/jre/lib/security/java.policy中的默认安全策略运行,在JDK 1.8中为如下内容:
// Standard extensions get all permissions by default
grant codeBase "file:$/*" {
permission java.security.AllPermission;
};
// default permissions granted to all domains
grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See the API specification of java.lang.Thread.stop() for more
// information.
permission java.lang.RuntimePermission "stopThread";
// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "listen";
// "standard" properies that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
以下面的代码举例:
package com.example
object Hello {
def main(args: Array[String]): Unit = {
val runtime = Runtime.getRuntime
val cwd = System.getProperty("user.dir")
val process = runtime.exec(s"$cwd/testscript.sh")
println("Process executed without security manager interference!")
}
策略文件
在启用安全管理器并使用一个额外的策略文件时,可以明确地启用或禁用执行特权:
// You can read user.dir
permission java.util.PropertyPermission "user.dir", "read";
// Gets access to the current user directory script
permission java.io.FilePermission "${user.dir}/testscript.sh", "execute";
permission java.util.PropertyPermission "scala.control.noTraceSuppression", "read";
你可以以下面的方式运行:
java -Djava.security.manager -Djava.security.policy=security.policy com.example.Hello
如果只注释掉FilePermission那一行,那么将会抛出一个异常。
到目前为止一切都比较顺利。但是,那只是对客户端一侧的applet启用了这个功能,此时在服务器端仍然是禁用状态。
这是为什么呢?其实,因为(可以查看上述代码)默认的SecurityManager将系统锁在了无用点上。为了使系统处于有用状态,它必须拥有一个自定义的java.security.policy文件。
问题分析
这种策略的实现中有几个问题。因为策略文件本身是过时了的,安全权限并未以任何一种逻辑顺序进行排序,且一些权限拥有通配符的选择功能而其他的则没有。你可以以白名单的形式只允许特定的行为,而不是直接拒绝。最坏的情况是,列表越长,那么程序将运行得越慢。这里有一个教程和权限列表,但是在实践中并不是特别有用,并且这份文档指南上一次的更新时间早在2002年。
当你有了不可信任的代码时,可以编写定制的SecurityManager,而这就是Scalatron所做的工作。
然而,如果我们要防止远程代码执行,那么我们需要一个通用目的的SecurityManager,其中它几乎允许所有事情,但是能够阻止在主机上运行的代码。这可能不是一个完美的防守,但是它将是深度防御策略中很重要的一部分。
事实上,已经有个人做了这一点。
pro-grade介绍
Josef Cacek 将pro-grade集成在一起,向Java文件中添加了一个“deny”选项和一个“allow”选项。Devoxx中有一个演讲以幻灯片和视频形式讲解了pro-grade。
现在,使用pro-grade进行前面的示例,下面的策略将会锁定所有执行权限:
priority "grant";
deny {
//https://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html#FilePermission permission java.io.FilePermission "<
>", "execute";
注意,这不是一个完整的解决方案。为了阻止代码工作,我猜想你还需要禁用其他几个权限,不过我还不清楚哪些权限是与此相关的。但是,这只是一个开始,为了廉价地确保服务器端的Java程序安全,还有很长的路要走。
使用Pro-grade很简单创建一个适当的策略。有一个策略生成器可以显示一个应用程序所需要的所有权限,且一个教程展示了创建它所需要的所有步骤,然后有一个权限调试器能够捕捉偏离的权限。
应用到你的项目中
你可以在prograde-example这里得到整个项目,并且可以从这里将pro-grade集成到你的项目中。此外,大多数人将需要Maven:
net.sourceforge.pro-grade
pro-grade
1.1.1
pro-grade真正有趣的地方是,它是一个透明的解决方案。虽然使用白名单策略是一种不错的方式,但是使用这种技术你可以将pro-grade添加到一个现有的、已经编译的项目目中,并且可以禁用脚本执行。
只需一些小小的修改,pro-grade就可以用来通知入侵(尤其是setSecurityManager和其他很少接触的地方),似乎在默默地矫正操作时也能够正常工作。你需要做的所有工作就是以一个SLF4J实现来实现PermissionDeniedListener。
查看评论 回复