前言

继续研究Java安全,这里简单说一下JNDI注入相关的,虽然网上已经有很多介绍了,但是这里作为记录还是稍微简单写下自己的调试过程吧(很久前写的文章了,最近在忙着秋招忘了发了,这里补充下)。

正文

JNDI(The Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API,命名服务将名称和对象联系起来,使得我们可以用名称访问对象。其中支持的有RMI,LDAP等,这里我们主要涉及的就是这两种。

RMI

专门为Java的远程方法调用,当本地没有相对应的类时,可以调用远程服务器的对象,攻击者构造一个RMI服务用来指定远程恶意类的位置,然后当被攻击者的lookup调用函数中的参数可控时,访问搭建的RMI服务,然后从而调用远程恶意类导致代码执行,我们看一下具体例子和调试。

这里用下别人的总结

  1. 目标代码中调用了InitialContext.lookup(URI),且URI为用户可控;
  2. 攻击者控制URI参数为恶意的RMI服务地址,如:rmi://hacker_rmi_server//name;
  3. 攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
  4. 目标在进行lookup()操作时,会动态加载并实例化Factory类,接着调用factory.getObjectInstance()获取外部远程对象实例;
  5. 攻击者可以在Factory类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,达到RCE的效果;

首先是Server端

看到我们在1099开启了一个服务,然后用Reference包装指定了在http://127.0.0.1:8555/Test远程恶意类,然后ReferenceWrapper类进行了包装

然后Client端

lookup访问rmi路径,访问rmi指定的恶意类地址,并触发,

然后看下恶意类Test.java

编译成.class文件,然后起一个web服务python -m SimpleHTTPServer 8555

然后分别开启Server和Clinet,注意jdk版本不能太高,否则会因为codebase不能信任远程工厂类而无法执行(低于JDK 6u132, JDK 7u122, JDK 8u113),这里我用的是jdk 1.6.0_65,这里推荐一个mac的java多版本管理工具jenv,挺好用的。最后成功弹出计算器

然后我们打下断点简单看下调用链,我们在exec处打上断点,然后开启debug

然后看下调用链

虽然具体jdk版本不同,但是只要不高于上面要求的版本,都大差不差,我们一步一步跟进来看具体逻辑,显示javax.naming.lookup

继续跟进com.sun.jndi.toolkit.url.lookup

我们不急着继续跟进,我们心啊看下getRootURLContext函数,会发现这是个接口,根据不同的协议来返回相应的实例

这里我们调用的是rmi这个实现,然后继续我们的跟进

registry.lookup()函数在注册中心中调用查找,然后跟进decodeObject方法

看下decodeObject,因为远程的ReferenceWrapper类实现了RemoteReference接口

,所以调用getReference方法获取到Reference类,然后继续跟进getObjectInstance方法

我们先继续跟进getObjectFactoryFromReference方法,这个函数会调用工厂类进行初始话代码段和无参构造函数,然后如果策划你公公给你获取那么就会调用这个ver4的getObjectInstance方法,这也是为什么我们在Test.java中重写了getObjectInstance方法(也可以把RCE代码放在这个方法里面就是因为这里会进行调用),如果没有这个重写会报错,但是其实我们的rce代码放在无参构造函数中其实是没有关系的,就算报错了也会执行。

接着我们继续跟进getObjectFactoryFromReference

可以看到先是尝试从本地classpath加载指定的类,没有的话就根据工厂类的名字和codebase加载远程类。

跟进下loadClass函数

利用的是URLClassLoader类去进行加载var1就是工厂类的名字,var3是根据codebase实例话的URLClassLoader类,

最后就是一系列的newInstance的操作进行初始化,调用到最后是native方法了(应该也是反射的东西),就不继续跟进了,最后成功exec()。

LDAP

同样先给代码例子

Server端

Client端的代码

同样是上面一样的Test.java不用改了

打上断点看下

调用链类似就不浪费笔墨了。

高版本绕过

有两种可能的方法,不过都依赖本地的配置或者调用链,具体分析之后再写

  1. 本地的classpath中找一个Reference Factory类来进行命令执行,例如javax.el.ELProcessor
  2. 利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行

总结

最近看了很多Java安全的,从fastjson到weblogic到struts2到shiro,感觉java反序列化才是大头,各种神奇的调用链,学习学习,加油吧。