Java 反序列化 - commons collection 之困(一)

#01多余的碎碎念

说到 java 反序列化,去搜索的话能看到网上有很多分析关于 commons collection 利用链的文章,emm 我一开始看不懂,看到很多代码的图头晕。

这篇文章的话其实是我跟着 p 神的文章一路走下来的,所以整个逻辑会按照 p 神的文章走。那对于反射、动态代理也有一些自己的理解,所以就记录下来。希望也给你们多一个角度的理解。

#02反射

为什么要先讲反射?因为你去看他的利用链的实现,会发现都会运用到反射。

java 中有两个类用来执行命令,一个是 Runtime ,一个是 ProcessBuilder 。那这两个类都是没有实现 Serializable (序列化)接口的,也就是不能进行反序列化,我们想想,如果我们执行命令的类不能进行反序列化,也就是不能利用反序列化漏洞还原该类,那我们是不是也就不能执行命令了?这时候我们其实就可以通过反射来创建该类的对象【也就是常说的运行时对象】,调用该类的方法。

所以我们首先来介绍一下反射,只要我们知道需要用的类名、方法名及参数类型,我们就能通过反射机制创建任意类对象、调用任意类方法。

举一较常用的例子:

复制代码
package com.govuln;

 import java.lang.reflect.InvocationTargetException;

 public class test {
     public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
         Class clazz = Runtime.class;
         clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(null), "calc.exe");
     }
 }

这里其实就是通过反射实现了调用计算器的效果,如果不用反射,我们正常代码逻辑就是 Runtime.getRuntime().exec("calc"); 就好了。

那么接下来为了理解上面的代码一步步看反射的语法。

我们从下往上推,最开始看他方法调用的部分:

正常方法调用语法:obj.方法(args)

反射方法调用语法:方法.invoke(Object obj, Object... args) ,invoke 中传入的参数为调用此方法的对象,及方法需传入的参数值。

而此时的方法也应是通过反射得到的。语法:class.getMethod(String name, Class... parameterTypes) ,getMethod 方法中传入的为我们需调用的方法名以及方法的参数类型。

而这里的 class 就是字节码文件对象,也是类在内存中的体现。字节码文件也就是 java 源文件编译后生成的 .class 文件,JVM 将 .class 文件加载到内存执行,将编译后的 .class 文件当作字节码文件对象。那么获取当前 class 的语法:

复制代码
1、类.class
2、Class.forName(类的全路径)

所以我们刚才代码中获取 Runtime 的字节码文件对象其实是通过第一种方式获取到的。

讲到这里前面代码中通过反射执行 Runtime 的 exec 方法应该可以理解得差不多:首先获取到 Runtime 的字节码文件,接下来获取到其方法 exec ,然后进行方法调用。那这里还有一个地方就是方法调用的时候可能不明白:invoke 传入的参数,调用此方法的对象。clazz.getMethod("getRuntime").invoke(null) 为什么是这样生成对象的呢?那其实反射获取到目标对象有两种方式,第一种就是通过上面代码中的形式,这种是通过 Runtime 去调用静态方法 getRuntime ,从而得到一个 Runtime 对象。

这里通过这种方式是因为 Runtime 类的构造方法是私有的(意味着非 Runtime 类无法访问到生成对象时需调用的构造方法,作用域的限制)。所以他有一个统一的路径去获取到 Runtime 对象,那就是通过调用静态方法 getRuntime 。那么调用静态方法时,传入的参数调用此方法的对象可以为 null,当然传入上面代码中 clazz 对象也是可以的。

到这里上面通过反射执行计算器的命令应该就都能理解了。我们还讲一下第二种方式来获取目标对象哦,那就是通过基本的构造方法来获取到目标对象,在反射中,我们可以通过

clazz.getDeclaredConstructor() 方法获取到类的私有构造方法,并设置 Accessible 为 true ,即可通过获取的构造器来 newInstance 获取到目标对象,newInstance 方法中传入的参数是创建目标对象时需要的初始化对象,没有则不传。所以也就是可以这样执行命令:

复制代码
Class clazz = Class.forName("java.lang.Runtime");
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Runtime runtime = (Runtime) constructor.newInstance();
clazz.getMethod("exec", String.class).invoke(runtime, "calc");

那么通过另一个类来执行命令的话,大家可以自己先写一下:

反射:

复制代码
Class clazz = ProcessBuilder.class;
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc")));

正常:

复制代码
ProcessBuilder processBuilder = new ProcessBuilder("calc");
processBuilder.start();

【------全网最全的网络安全学习资料包分享给爱学习的你,关注我,私信回复"资料领取"获取------】
1.网络安全多个方向学习路线2.全网最全的CTF入门学习资料3.一线大佬实战经验分享笔记4.网安大厂面试题合集5.红蓝对抗实战技术秘籍6.网络安全基础入门、Linux、web安全、渗透测试方面视频

#03动态代理

为什么要讲动态代理?emmm 因为利用链会用到。

动态代理,其实挺像相亲的【也到了要相亲的年纪,也挺和时宜 233 】。当男孩心悦于某女孩,那么肯定都会先要经过父母这关的,当父母觉得这个男孩子 ok ,那么好,你们俩去进行下一步操作。那这里父母其实就相当于我们的动态代理类,其实也相当于一个拦截过滤器的作用,使其不直接与类对象进行交互而是通过代理对象去进行交互,想要调用该类对象的某方法时:

  1. 首先获取到通过代理类生成的代理对象:Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 返回某个对象的代理对象。
  • 第一个参数是 ClassLoader ,指明生成代理对象使用哪个类装载器;
  • 第二个参数是指明生成哪个对象的代理对象,通过接口指定;
  • 第三个参数是一个实现了 InvocationHandler 接口的对象,该对象有个 invoke 方法里面实现了代理对象要去做什么事的逻辑。
  1. 接下来通过代理对象调用目标对象的方法,实际上先调用了 h 的 invoke 方法,接着再去调用具体对象的方法。

可以看一个例子理解:

复制代码
public class App {
    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("hello", "world");
        String result = (String)proxyMap.get("hello");
        System.out.println(result);
    }
}

public class ExampleInvocationHandler implements InvocationHandler {

    protected Map map;

    public ExampleInvocationHandler(Map map){
        this.map = map;
    }

    @Override
    public 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);
    }
}

运行结果:

#04不重要的结尾

好啦,讲到这里文章已经很长了,下一篇再一起分析 commons collection 的利用链吧。

相关推荐
慌糖3 分钟前
RabbitMQ:消息队列的轻量级王者
开发语言·javascript·ecmascript
风象南10 分钟前
SpringBoot 控制器的动态注册与卸载
java·spring boot·后端
醇醛酸醚酮酯28 分钟前
Qt项目锻炼——TODO清单(二)
开发语言·数据库·qt
jioulongzi33 分钟前
记录一次莫名奇妙的跨域502(badgateway)错误
开发语言·python
我是一只代码狗36 分钟前
springboot中使用线程池
java·spring boot·后端
hello早上好1 小时前
JDK 代理原理
java·spring boot·spring
PanZonghui1 小时前
Centos项目部署之Java安装与配置
java·linux
向阳@向远方1 小时前
第二章 简单程序设计
开发语言·c++·算法
沉着的码农1 小时前
【设计模式】基于责任链模式的参数校验
java·spring boot·分布式
Mr_Xuhhh2 小时前
信号与槽的总结
java·开发语言·数据库·c++·qt·系统架构