CommonsCollections 1分析

CommonsCollections 1分析

利用链

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的利用中使用的是LazyMap,而在LazyMap中需要用到动态代理。

动态代理

官方文档

InvocationHandler

public interface InvocationHandler

InvocationHandler是由代理实例的调用处理程序实现的接口 。
每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。

Modifier and TypeMethod and Description
Objectinvoke(Object proxy, 方法 method, Object[] args)处理代理实例上的方法调用并返回结果。

Proxy

public class Proxy
extends Object
implements Serializable

Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
为某个接口创建代理Foo :

InvocationHandler handler = new MyInvocationHandler(...);
 	Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
 	Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
                 newInstance(handler); 
或更简单地:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                      new Class<?>[] { Foo.class },
                                      handler); 
  • 每个代理类有一个公共构造一个参数,该接口的实现InvocationHandler ,设置调用处理程序的代理实例。 而不必使用反射API来访问公共构造函数,也可以通过调用Proxy.newProxyInstance方法来创建代理实例,该方法将调用Proxy.getProxyClass的操作与调用处理程序一起调用构造函数。

这里来举个例子:

这里先定义一个handler实现对接口的调用。

然后再创建一个hello对象,因为所有方法都会委派给invoke,而重写的invoke方法中当使用get方法时会打印Evil。最后在输出中打印Evil。

利用链分析

在commons collections中有一个Transformer接口,其中包含一个Transfrome方法,我们可以通过此接口来完成cc1的利用。

在众多的继承中,我们主要利用到三个

InvokerTransformer

public InvokerTransformer(String methodName,
              Class[] paramTypes,
              Object[] args)

Constructor that performs no validation. Use getInstance if you want that.
Parameters:

methodName - the method to call
paramTypes - the constructor parameter types, not cloned
args - the constructor arguments, not cloned

Transforms the input to result by invoking a method on the input.

这个方法对input进行反射。

ConstantTransformer

其Transformer将输入原封不动的返回。

public Object transform(Object input)
Transforms the input by ignoring it and returning the stored constant instead.

Specified by:
transform in interface Transformer
Parameters:
input - the input object which is ignored
Returns:
the stored constant

ChainedTransformer

这三个组合起来就可以实现任意执行


这样就可以弹出计算器了,但是这里反序列化的时候会发生报错。因为Runtime并没有继承Serializable,所以这里会序列化失败。刚刚开始我还傻傻的以为这是不能命令执行,结果我发现我的可以执行(小丑竟是我自己)。

因此我们要用反射的方式来调用getRuntime这个方法。

接下来我们就要思考中cc1中哪里触发了transform方法,这里是LazyMap中的get方法触发

可以看见当get的键不存在时,会触发transform方法。

protected final Transformer factory;

而且这里的factory也是可控的。现在我们找到了transform的触发点LazyMap#get,因此我们接下来应该找到readObject的触发点了。下面便要用到我们开头讲过的动态代理了,在AnnotationInvocationHandler中有使用到readObject方法。

那么我们就让这里的memberValues为代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)。

因为代理类调用方法会触发invoke方法,而invoke方法中又使用到了get方法(在下面代码的最后一行),所以我们只要设置两个代理就可以触发。

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

	private final Class<? extends Annotation> type;
	private final Map<String, Object> memberValues;
	private transient volatile Method[] memberMethods = null;

	/* 后续无关代码就省略了,想看的话可以查看 sun/reflect/annotation/AnnotationInvocationHandler */
}

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);

我这运行最终poc中遇到了如下的bug:


这是因为java版本过高导致的,这里的版本应该是1.7.

最终也成功的弹出了计算器。这里设置了两个代理有点绕,第一个代理是用来触发代理类中的get的,第二个代理是来触发invoke的。

参考链接

https://www.wjlshare.xyz/archives/1502

https://paper.seebug.org/1242/#commonscollections-1

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值