JNDI注入分析
lookup过程
示例代码:
1 | import javax.naming.Context; |
首先跟进 javax.naming.InitialContext#lookup中,其会调用 javax.naming.InitialContext#getURLOrDefaultInitCtx方法传入 name
参数。
判断语句中调用 javax.naming.spi.NamingManager#hasInitialContextFactoryBuilder方法,当 NamingManager类的 initctx_factory_builder
属性不为null则返回true
,反之为 false
。getURLScheme方法通过截取 :
前的字符作为协议。
javax.naming.spi.NamingManager#getURLContext 调用 getURLObject方法,传入的参数为
scheme(协议),null,null,null,environment(空Hashtable对象)
进入getURLObject调用 com.sun.naming.internal.ResourceManager#getURLObject,传入参数:
“java.naming.factory.url.pkgs”
environment(空Hashtable对象)
null
“.rmi.rmiURLContextFactory, com.sun.jndi.url”
因为 env
变量不存在 java.naming.factory.url.pkgs键,因此 facProp
变量为 defaultPkgPrefix
。其值为 com.sun.jndi.url
调用 com.sun.naming.internal.VersionHelper12#getContextClassLoader 获得类加载器。
加载 com.sun.jndi.url.rmi.rmiURLContextFactory类返回实例对象。
com.sun.jndi.url.rmi.rmiURLContextFactory#getObjectInstance,参数为:
null,null,null,environment(空Hashtable对象)
rmiURLContext构造方法中荟调用父类构造方法并传入接收的参数
rmiURLContext 继承自 GenericURLContext类,此处 return 回到 javax.naming.InitialContext#lookup
rmiURLContext 不存在 lookup
方法, 因此跟进父类 com.sun.jndi.toolkit.url.GenericURLContext#lookup
getRootContext 中解析出 IP和端口 实例化 com.sun.jndi.rmi.registry.RegistryContext对象,返回 javax.naming.spi.ResolveResult 对象:
调用 com.sun.jndi.rmi.registry.RegistryContext#lookup,传入查询的对象名
lookup 中通过 sun.rmi.registry.RegistryImpl_Stub#lookup 获得 ReferenceWrapper 对象,接着调用 RegistryContext#decodeObject。
跟进 decodeObject 方法调用了 NamingManager.getObjectInstance,其传入的参数为 Reference 对象和 CompositeName对象(保存了查询的对象名称)。
NamingManager#getObjectInstance 315行获取类名然后调用 NamingManager#getObjectFactoryFromReference,f
变量是服务器返回的类名。
146行会从本地classpath加载这个类,如果类不存在触发ClassNotFound异常会走到158行处通过codebase远程加载类。而codebase是 Reference
对象中指定的 factory
,获取类的Class后调用 newInstance
方法获取实例对象。
如果远程加载的类静态代码块中存在恶意代码的话则可以造成RCE。
堆栈信息:
1 | getObjectFactoryFromReference:160, NamingManager (javax.naming.spi) |
< JDK8u191
< JDK8u121 可以通过RMI远程加载恶意类,使用JNDI-Inject-Exploit 开启服务:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C “calc.exe” -A “ip”
> JDK8u121 < JDK8u191 则可以使用LDAP进行远程恶意类加载
> JDK8u191
JDK8u191以后的版本RMI和LDAP的useCodebaseOnly默认为false,即无法通过远程加载类,这样一来JNDI注入到方式就变得十分局限。
前面在debug时能够发现在 NamingManager#getObjectFactoryFromReference 加载类时会先通过本地classpath加载,当本地classpath不存在类时才会从codebase加载类。而 > JDK8u191的利用思路则是通过本地classpath中的类寻找可利用的 getObjectInstance
方法(这个类必须实现ObjectFactory接口)。
举个例子,我们本地编写一个 Payload
类实现 javax.naming.spi.ObjectFactory 接口,并在 getObjectInstance
方法中执行 calc.exe
命令。
1 | package org.vulhub.jndi; |
加载本地的类返回实例对象:
然后回到 javax.naming.spi.NamingManager#getObjectInstance 会调用该对象的 getObjectInstance
方法。
触发RCE
Veracode的博客中使用了org.apache.naming.factory.BeanFactory类,环境依赖Tomcat 8+或Spring 1.2.x。具体的攻击方法已有项目实现:https://github.com/welk1n/JNDI-Injection-Bypass