JSM Bypass via createClassLoader

JSM(Java Security Manager),又名java安全管理器,经常被用于java进程中的一些权限限制和保护的作用,类似 php 中的 disable_functions ,但 disable_functions 主要是针对函数的禁用操作,而JSM更偏向于运行时的授权检查,根据配置好的安全策略来执行。一般启用 JSM 只要在启动 java 进程的时候加入参数即可,比如:

而/home/yourPolicy.policy则是用户自行配制的策略文件,一般长这样:

这就表示这个java进程对secret文件只有只读的权限。而本文主要总结一下当策略文件被赋予了”createClassLoader”时,能够bypass整个JSM策略的方法。当然,createClassLoader只是最基础的一个权限,其实很多类似的权限都有同样的问题。

其实很早之前空虚浪子心(kxlzx)就有写到过他是如何利用 creatClassLoader 来绕过SAE的沙箱,笔者只是在此基础上做了一个延伸。先来看下 createClassLoader 这个权限是个啥东西,简单来说,createClassLoader就是能够让你的 java 程序拥有建立一个 ClassLoader 的权限。而ClassLoader又是什么呢?我们都知道java程序会编译成 class 文件最终由 JVM 加载执行,ClassLoader则是一个容器或者说是加载器,把java程序涉及的class文件都加载到内存里来,这样 java 里需要引用到其他类就能够在一个容器里成功引用了。

而利用 createClassLoader 来绕过权限检查的原理则是我们拥有建立一个自己的ClassLoader的权限,我们完全可以在这个 ClassLoader 中建立自己的一个class,并赋予一个新的JSM策略,这个策略也可以是个null,也就是关闭了整个java安全管理器。核心在 ClassLoader 存在一个方法叫 defineClass ,defineClass允许接受一个参数 ProtectionDomain ,我们能够自建一个 ProtectionDomain 将自己配制好的权限设置进去,define 出来的 class 则拥有新的权限。核心绕过的代码如下:

首先新建的类需要继承ClassLoader类,然后看到loadIt方法,在调用defineClass的时候,带入的最后一个参数localProtectionDomain是我们新建的一个权限域,而里边拥有的权限则为AllPermission;最后产生的localClass是啥呢,从代码看到其实是Payloader.class这个类,这个类的代码如下:

从代码中就能看出来,其实核心的就是 System.setSecurityManager(null); 将整个安全管理器设置为null,达到 bypass 的效果。此时 localClass.newInstance() 之后我们再执行相关的任意文件读取或者是命令执行,就不会再提示没有权限执行了。

值得注意的是,createClassLoader权限其实经常会被开放出来,是由于其本身业务使用到了createClassLoader权限,如果禁止掉,则很有可能业务跑不起来,所以这个权限经常会被开放。譬如在Jython环境中,很多时候也用到了 JSM 策略来防止用户执行意料之外的 Jython 代码。但为了保证Jython代码能够正常运行,通常会开放 createClassLoader ,此时我们也能够利用上述方法来bypass安全策略。我们只需要调用:

则为调用父类ClassLoader的defineClass方法来绕过安全策略。原理都是类似的,只是把java的语法转换成python语法罢了。但之前笔者发现,有人在Jython中做了个脚本语言层面的检查,将用户提交的脚本的defineClass这个函数给禁用掉了。导致我们无法在代码里调用defineClass。但是没有关系,在Jython的官方API中,提供了一个名叫 org.python.core.BytecodeLoader.Loader的 包,里面含有一个方法叫loadClassFromBytes,在官方文档中是这么定义的:

其实如果看 Jython 的源代码可以发现,loadClassFromBytes的实现其实也是调用了 defineClass ,不过细心的同学可以发现,loadClassFromBytes方法没有提供 ProtectionDomain 这个参数了,那该怎么利用它来 bypass 呢。没错,聪明的你可能已经想到了。我们可以利用 loadClassFromBytes 方法来加载一个类A,而被加载的类A中可以再调用 defineClass 来再加载另一个类B。而这个时候就能够在类A中使用defineClass时加入自己的ProtectionDomain了,机智如我。类A的代码如下:

类B的话其实就是 getPayload 方法中的一堆byte数组了。而类B的代码就是上文提到的System.setSecurityManager(null)的类。用来关闭整个安全管理器的。然后紧接着将类A转化成byte数组,再利用 Jython 的 loadClassFromBytes 来加载,运行。吧唧,运行出错,傻眼了。报了一个defineClass时超过长度限制的错误,经过一番代码优化,发现类A的大小还是不符合,依旧超过长度限制。经过一番搜索研究发现,这里有个小tips能够在java编译成class文件时压缩大小,让编译出来的class文件尽可能小,利用的命令如下:

编译出来的 class 文件再转成bytes数组,再次利用 loadClassFromBytes ,此时终于成功运行,最终绕过了 JSM 策略和 Jython 中禁用 defineClass 函数的限制,成功执行命令了。

参考资料

SAE云服务安全沙箱绕过2(利用crackClassLoader)

http://alphaloop.blogspot.com/2014/08/sandboxing-python-scripts-in-java.html

http://blog.csdn.net/cnbird2008/article/details/18095133

via:https://www.n0tr00t.com/2016/12/30/jsm-Bypass-via-CreateClassLoader.html