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 的利用链吧。

相关推荐
kehua_gao2 分钟前
JMeter 5.6.3 jdk 1.8 不能录制https网页脚本的解决办法
java·jmeter·https
qq_447663053 分钟前
Spring学习之路:环境搭建、核心API与配置文件细节
java·后端·spring
tangweiguo030519876 分钟前
Java 和 Kotlin 实现 23 种设计模式:从理论到实践
android·java·kotlin
小峰编程16 分钟前
Python数据类型进阶——详解
linux·运维·服务器·开发语言·python·pycharm·swift
m0_4669238017 分钟前
【java】字符串存储的内存原理
java·开发语言
狗头大军之江苏分军22 分钟前
移动端直播卡顿如何实时检测且告知用户
java·前端·后端
Rverdoser34 分钟前
conda创建Python虚拟环境的原理
开发语言·python·conda
blammmp40 分钟前
Java EE 进阶:SpringBoot 配置⽂件
java·spring boot·java-ee
kfepiza42 分钟前
Java使用JDBC连接操作Sqlite 笔记250314
java·sqlite