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

相关推荐
m0_516484678 分钟前
C#winform多选框代码
开发语言·c#
啾啾Fun29 分钟前
Java反射操作百倍性能优化
java·性能优化·反射·缓存思想
20岁30年经验的码农36 分钟前
若依微服务Openfeign接口调用超时问题
java·微服务·架构
曲莫终44 分钟前
SpEl表达式之强大的集合选择(Collection Selection)和集合投影(Collection Projection)
java·spring boot·spring
ajassi20001 小时前
开源 java android app 开发(十二)封库.aar
android·java·linux·开源
q567315231 小时前
Java使用Selenium反爬虫优化方案
java·开发语言·分布式·爬虫·selenium
kaikaile19951 小时前
解密Spring Boot:深入理解条件装配与条件注解
java·spring boot·spring
守护者1701 小时前
JAVA学习-练习试用Java实现“一个词频统计工具 :读取文本文件,统计并输出每个单词的频率”
java·学习
bing_1582 小时前
Spring Boot 中ConditionalOnClass、ConditionalOnMissingBean 注解详解
java·spring boot·后端
ergdfhgerty2 小时前
斐讯N1部署Armbian与CasaOS实现远程存储管理
java·docker