代码审计之文件下载

admin 2025年3月24日12:25:20评论10 views字数 3677阅读12分15秒阅读模式

介绍

本篇为代码审计系列的第八篇《代码审计之文件下载》,预计本系列为30篇左右。

对于一些场景下,系统功能可能需要查看一些文件的内容,或者去下载一些文件,比如说查看日志功能,程序会读取对应的日志文件过来,再比如下载附件功能或者下载一些办公文档的功能,类似于这种获取文件内容的或者直接下载文件的,当未对相关路径做严格过滤时,就会导致任意文件下载问题。

示例

我们来看下存在问题的文件下载代码是怎么样的,示例如下:

@RestController
publicclassDownloadController{

// 存在任意文件下载漏洞的接口
@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(@RequestParam("file") String filePath) throws IOException {
        File file = new File(filePath); // 漏洞点:直接使用未验证的用户输入作为文件路径

// 读取文件内容
        FileInputStream fis = new FileInputStream(file);
byte[] bytes = newbyte[(int) file.length()];
        fis.read(bytes);
        fis.close();

// 设置响应头强制下载
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition""attachment; filename=" + file.getName());

return ResponseEntity.ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(bytes);
    }
}

上面代码直接接收了前端传来的filePath参数值,然后去获取了该值所指的文件内容进行了返回,因为没有存在过滤和校验,会导致任意文件下载问题。

修复方式上,我们可以把下载的路径去固定一下,判断最终的路径是不是以我们指定的路径开头的,如下修复后的代码:

privatestaticfinal String BASE_DIR = "/var/log/";

@GetMapping("/secure-download")
public ResponseEntity<byte[]> secureDownload(
@RequestParam("file") String filename) throws Exception {

// 1. 路径规范化处理(防御路径遍历)
        Path resolvedPath = Paths.get(BASE_DIR, filename).normalize();

// 2. 验证路径是否在允许的目录范围内
if (!resolvedPath.startsWith(BASE_DIR)) {
thrownew SecurityException("非法文件路径访问");
        }

        File file = new File(resolvedPath);

// 读取文件内容
        FileInputStream fis = new FileInputStream(file);
byte[] bytes = newbyte[(int) file.length()];
        fis.read(bytes);
        fis.close();

// 设置响应头强制下载
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition""attachment; filename=" + file.getName());

return ResponseEntity.ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(bytes);
    }
}

其中Paths.get接收的是我们固定好的路径和传入的路径,经过normalize规范化后,会解析路径中的../这种,比如我固定路径是/var/logs/然后接收的路径是../../../../../etc/passwd,那么最后Paths返回的路径就是/var/logs/../../../../../etc/passwd,../解析后最后的路径就是/etc/passwd。

到这一步是不能阻止任意文件下载的,重要的在下面那个if判断,我们会判断Paths返回的路径是否以/var/logs开头,从而限定了下载目录。

这种方式是优先推荐的,如果目录不能固定,还有一种措施是可以限制文件名或文件后缀,以白名单形式来进行校验。

// 白名单配置
privatestaticfinal Set<String> ALLOWED_EXTENSIONS = Set.of("pdf""txt""jpg");
privatestaticfinal Set<String> ALLOWED_FILENAMES = Set.of("license.txt""README.md");

@GetMapping("/secure-download")
public ResponseEntity<byte[]> secureDownload(
@RequestParam("file") String filename) throws Exception {
        Path path= Paths.get(filename);

if (!isFilenameAllowed(path.getFileName().toString())) {
thrownew SecurityException("文件名不在允许列表中");
        }

if (!isExtensionAllowed(path.getFileName().toString())) {
thrownew SecurityException("文件类型被禁止");
        }

        File file = new File(path);

// 读取文件内容
        FileInputStream fis = new FileInputStream(file);
byte[] bytes = newbyte[(int) file.length()];
        fis.read(bytes);
        fis.close();

// 设置响应头强制下载
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition""attachment; filename=" + file.getName());

return ResponseEntity.ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(bytes);
    }
}

其中判断文件名的函数如下:

privatebooleanisFilenameAllowed(String filename){
return ALLOWED_FILENAMES.contains(filename);
}

判断文件后缀的代码如下:

privatebooleanisExtensionAllowed(String filename){
    String extension = extractExtension(filename).toLowerCase();
return ALLOWED_EXTENSIONS.contains(extension);
}

private String extractExtension(String filename){
int lastDotIndex = filename.lastIndexOf('.');
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
return""// 无扩展名或以点结尾
    }
return filename.substring(lastDotIndex + 1);
}

另外,最好不要用黑名单过滤,比如检测是否存在../这种,这种可能存在一些绕过方式,比如路径编码或者传入绝对路径等。

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

关于我们

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

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

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

发表评论

匿名网友 填写信息