Spring 场景下突破 pebble 模板注入限制

本文详细介绍了如何在Spring环境中利用Pebble模板引擎的漏洞进行目录穿越、RCE攻击路径构建、绕过限制加载Class对象等技巧。作者通过分析源码和调试,展示了从上传恶意模板文件到实现远程代码执行的完整过程,涉及Spring MVC、文件上传、模板注入等多个方面。
摘要由CSDN通过智能技术生成

写在前面

之前周末忙着强网杯,对这道题只做了一半就搁置下来了,最后卡在绕过最新pebble模板引擎RCE那里,今天抽空来继续进行剩下的分析,正好题目里有几个在现实场景当中能用的trick顺便也分享了

题目环境分析

也是挺不错题目直接给了docker环境便于本地搭建,同时设置了权限需要执行./getflag才能获取获得flag

FROM openjdk:18-slim-bullseye

RUN mkdir /usr/src/app
WORKDIR /usr/src/app

# create user
RUN groupadd chalusr
RUN useradd -ms /bin/bash -g chalusr chalusr

COPY spoink/target/spoink-0.0.1-SNAPSHOT-spring-boot.jar ./
COPY spoink/public ./public
COPY spoink/templates ./templates
COPY getflag ./

RUN chmod 111 ./getflag

USER chalusr
CMD ["java", "-jar", "/usr/src/app/spoink-0.0.1-SNAPSHOT-spring-boot.jar"]

路由只有一个,根据参数x返回指定模板,刚看到这里的时候其实有点懵,毕竟很少见到只给一个路由的代码

@Controller
public class HomeController {
    public HomeController() {
    }

    @RequestMapping({"/"})
    public String getTemplate(@RequestParam("x") Optional<String> template, Model model) {
        return (String)template.orElse("home.pebble");
    }
}

不过我很快关注到了一个application.properties当中一个很有趣的点,也就是这里没有后缀,因此想到了一个目录穿越的可能

pebble.prefix = templates
pebble.suffix =

正文

目录穿越

为什么我说上面那个点很有趣,其实就是第一个想分享的trick,路径穿越,简单来说pebble当中有两个loader一个是classpathloader,另一个是fileloader,优先会在classpath下尝试加载模板文件,如果寻找不到则使用fileloader尝试加载模板文件,其他调用栈不是很重要这里就不多提了

既然想实现任意文件读那第一个就别想了,我们来看第二个,它在com.mitchellbosecke.pebble.loader.FileLoader#getFile最终加载模板文件内容

可以很明显看到这里没有做路径限制,导致我们可以进行跨目录读任意文件

结果如下

RCE攻击路径初步构建

因此我们便能成功想到一条能RCE的攻击路径

  1. 上传带恶意内容的模板文件到目标服务器
  2. 利用LFI读取这个模板并RCE

如何上传文件?上传了如何获取?

但是这里就遇到第一个难点,如何上传文件?这里路由当中并没有上传文件的功能点

怎么办?其实很简单,我们也知道,我们的Spring MVC框架是围绕DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染和 文件上传 等功能,好了我都圈出来重点了

在这过程当中它会检查这是否是一个表单请求

正好我们也知道spring默认使用内置的tomcat引擎,

在处理表单的内容当中这会调用 org.apache.catalina.connector.Request#getParts 去处理解析内容,而这在之前的文章Tomcat文件上传流量层面系列文章当中也提到过,遗忘的可以去 我的博客 考古

废话不多说,类似php的处理一样,它会先将上传的文件保存到一个临时目录再最终复制到目标文件夹,临时文件夹的获取在哪里,在 org.apache.catalina.connector.Request#parseParts

发现是通过 javax.servlet.MultipartConfigElement#getLocation 函数获取到保存到临时路径

不难看到这里是空对吧,也就是默认值(默认的话后面会存到/tmp目录下),顺便多提一下,哪里可以设置这个location呢

在spring的启动过程当中,会根据 spring.servlet.multipart.location 的值设置这个内容,具体可以自行去参考org.springframework.boot.autoconfigure.web.servlet.MultipartProperties

@ConfigurationProperties(
    prefix = "spring.servlet.multipart",
    ignoreUnknownFields = false
)
public class MultipartProperties {
    private boolean enabled = true;
    private String location;
    private DataSize maxFileSize = DataSize.ofMegabytes(1L);
    private DataSize maxRequestSize = DataSize.ofMegabytes(10L);
    private DataSize fileSizeThreshold = DataSize.ofBytes(0L);
    private boolean resolveLazily = false;

    public MultipartProperties() {
    }

    public boolean getEnabled() {
        return this.enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getLocation() {
        return this.location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public DataSize getMaxFileSize() {
        return this.maxFileSize;
    }

    public void setMaxFileSize(DataSize maxFileSize) {
        this.maxFileSize = maxFileSize;
    }

    public DataSize getMaxRequestSize() {
        return this.maxRequestSize;
    }

    public void setMaxRequestSize(DataSize maxRequestSize) {
        this.maxRequestSize = maxRequestSize;
    }

    public DataSize getFileSizeThreshold() {
        return this.fileSizeThreshold;
    }

    public void setFileSizeThreshold(DataSize fileSizeThreshold) {
        this.fileSizeThreshold = fileSizeThreshold;
    }

    public boolean isResolveLazily() {
        return this.resolveLazily;
    }

    public void setResolveLazily(boolean resolveLazily) {
        this.resolveLazily = resolveLazily;
    }

    public MultipartConfigElement createMultipartConfig() {
        Mult
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值