代码审计之文件上传

admin 2025年3月15日17:30:57评论1 views字数 3817阅读12分43秒阅读模式

介绍

本篇为代码审计系列的第七篇《代码审计之文件上传》,预计本系列为30篇左右。

对于文件上传功能,如果后端没有对上传的文件做过滤和校验,则会导致可上传任意文件到服务器,如果后端根据文件名去拼接一个路径,把文件传到对应目录下,当文件名没有进行过滤时,也可造成文件传到任意目录下,相当于上一节说的目录遍历。

示例代码

文件上传目前常用的方式就是MultipartFile,示例代码如下:

privatestaticfinal String UPLOADED_FOLDER = System.getProperty("user.dir") + "/src/main/resources/static/file/";

@GetMapping("/uploadStatus")
public String uploadStatus(){
return"upload";
}

@ApiOperation(value = "vul:上传任意文件")
@PostMapping("/uploadVul")
public String uploadVul(@RequestParam("file") MultipartFile file,
                        RedirectAttributes redirectAttributes) 
{
if (file.isEmpty()) {
        redirectAttributes.addFlashAttribute("message""请选择要上传的文件");
return"redirect:uploadStatus";
    }

try {
byte[] bytes = file.getBytes();
        Path dir = Paths.get(UPLOADED_FOLDER);
// path定义了文件所保存的目录,未对文件名做过滤
        Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());

if (!Files.exists(dir)) {
            Files.createDirectories(dir);
        }
// 未对文件做校验,直接进行了上传
        Files.write(path, bytes);
        redirectAttributes.addFlashAttribute("message",
"上传成功:" + path);

    } catch (Exception e) {
return e.toString();
    }
return"redirect:uploadStatus";
}

上述代码在接收到前端传来的文件时,直接进行了写入的操作,未对文件做类型判断,所以存在文件上传问题,同时,文件所存的目录直接拼接了文件名,未对文件名做过滤,可导致文件传到任意目录下面。

下面我们来看一个比较全面防护的示例代码,对文件后缀、大小、文件名都进行了相关过滤:

@RestController
@RequestMapping("/file")
@Api(tags = "FileController", description = "文件上传接口")
publicclassFileController{

privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(FileController.class);

// 允许上传的文件后缀(白名单)
privatestaticfinal Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList(
"jpg""jpeg""png""gif"
    ));

// 文件大小限制(10MB)
privatestaticfinallong MAX_FILE_SIZE = 10 * 1024 * 1024;

@Value("${file.upload.path:/upload}")
private String uploadPath;

@PostMapping("/upload")
@ApiOperation("文件上传")
public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file) {
try {
// 1. 空文件检查
if (file.isEmpty()) {
return CommonResult.failed("上传失败:文件为空");
            }

// 2. 文件大小检查
if (file.getSize() > MAX_FILE_SIZE) {
return CommonResult.failed("上传失败:文件大小超过限制");
            }

// 3. 获取文件名和后缀
            String originalFilename = file.getOriginalFilename();
            String extension = getFileExtension(originalFilename);

// 4. 文件后缀检查
if (!isValidFileExtension(extension)) {
return CommonResult.failed("上传失败:不支持的文件类型");
            }

// 5. 生成新的文件名(防止文件名冲突)
            String newFilename = generateUniqueFilename(extension);

// 6. 确保上传目录存在
            File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
                uploadDir.mkdirs();
            }

// 7. 构建完整的文件路径
            Path filePath = Paths.get(uploadPath, newFilename);

// 8. 保存文件
            Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);

return CommonResult.success(newFilename, "上传成功");

        } catch (IOException e) {
            LOGGER.error("文件上传失败", e);
return CommonResult.failed("上传失败:" + e.getMessage());
        }
    }

/**
     * 获取文件后缀(小写)
     */

private String getFileExtension(String filename){
if (filename == null || filename.lastIndexOf(".") == -1) {
return"";
        }
return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
    }

/**
     * 验证文件后缀是否合法
     */

privatebooleanisValidFileExtension(String extension){
return ALLOWED_EXTENSIONS.contains(extension.toLowerCase());
    }

/**
     * 生成唯一的文件名
     */

private String generateUniqueFilename(String extension){
        String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String random = UUID.randomUUID().toString().substring(08);
return timestamp + "_" + random + "." + extension;
    }

上面这个代码通过白名单验证了文件后缀,这种是最安全的一种方式,然后文件在保存时,为了避免文件名重复,会重命名,这样就不会存在目录遍历的问题。

如果不用随机命名,则需要对文件名进行校验,这种情况的防护代码和目录遍历中的就基本一样了,可以参考目录遍历章节,另外,最好用后缀的检测方式,如果只检测Conent-Type,则依然可以传任意文件,比如下面这段代码只检测Content-Type:

String contentType = file.getContentType();
if (!"image/jpeg".equals(contentType) && !"image/png".equals(contentType)) {
    redirectAttributes.addFlashAttribute("message""不允许上传该类型文件!");
return"redirect:uploadStatus";
}

以上就是关于代码审计中任意文件上传的相关内容,感谢阅读。

关于我们

我们是《AI安全攻防》,致力于分享AI安全、渗透测试、代码审计等内容,欢迎您的关注!

原文始发于微信公众号(AI安全攻防):代码审计之文件上传

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

发表评论

匿名网友 填写信息