文件上传漏洞防范-文件类型检测

文件上传漏洞防范-文件类型检测

      当时需要开发一个功能,管理员可以上传一个包含不良词语的文本文件。系统利用这些词语实时检查用户提交的内容。上传的文件需要遵循特定的格式。

为了防止用户上传文本文件以外的文件,我们可以在前端进行操作。

<input type="file" accept="text/plain" />

     这样,用户只需在文件选择窗口中选择一个文本文件即可。然而,为了确保系统安全,仅仅在界面上阻止用户是不够的。有必要在后台重新验证上传的文件,看看用户是否上传了文本文件。我们需要解决的问题是确定用户上传文件的实际类型。

开始

说明上述问题,我们将建立一个演示系统,前端使用 React.js,后端使用 Java/Spring Boot。

我们的界面非常简单,由一个输入[type=file]和一个上传所选文件的按钮组成。选择文件时,用户界面将显示浏览器确定的 MIME 类型。上传文件后,系统将返回后台确定的 MIME 类型。所有源代码都在这里。此外,还要准备一些文件,以测试系统判断是否正确。

准备 3 个扩展名正确的文件,然后复制这些文件并重命名:

real.png -> fake.txt

real.jpg -> fake.zip

real.svg -> fake.docx

后台文件类型确定

项目的后台系统使用 Spring Boot 以 Java 编写。还实现了一个控制器,用于接收用户的上传请求。

@Slf4j
@RestController
public class UploadController {

  @PostMapping(path = "/check-file-type", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
   public ResponseEntity < Response > checkFileType(@RequestPart MultipartFile file) {
     // to be implemented
   }
}

以及将结果返回给用户的响应

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Response {
   private int status;
   private String message;
   private String mimeType;

  public Response(String mimeType) {
     this.status = HttpStatus.OK.value();
     this.message = "Successful";
     this.mimeType = mimeType;
   }
}

使用用户代理定义的 MIME 类型

当从 input[type=file] 中选择文件时,文件类型已由浏览器(用户代理)根据 MIME 类型格式确定,然后通过 Content-Type 请求头传输到后端。因此,控制器参数中的 MultipartFile 类已经包含了文件类型的信息。

现在,您可以使用 getContentType() 根据 MIME 类型确定文件类型。

@PostMapping(path = "/check-file-type", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity < Response > checkFileType(@RequestPart MultipartFile file) {
   String mimeType = file.getContentType();
   return ResponseEntity.ok(new Response(mimeType));
}

让我们测试一下上面准备的文件

     就文件 real.png 而言,用户代理通过 .png 扩展名识别出了正确的 MIME 类型。但对于 fake.zip 文件,用户代理无法正确识别其文件类型为 JPG,而是通过 .zip 扩展名来确定。因此,当用户有意更改文件名和扩展名时,依赖客户端定义的 MIME 类型可能会有一些风险。每种文件类型都有不同的规范,存储方式也不同,因此如果要确定文件的确切类型,就需要读取该文件的内容。

     MIME 类型和确定文件类型的一些方法 MIME 类型(多用途 Internet 邮件扩展)是一种定义文档、文件或字节集的性质和格式的标准。它在 IETF 的 RFC 6838 中进行了定义和标准化。MIME 类型的结构包括类型和子类型:

类型/子类型 示例:text/plain、application/zip、...

详细说明

      类型是数据类型所属的一般类别,如视频或文本。 子类型决定所分类的确切数据类型。例如对于文本类型,我们可以有 plain(纯文本)、html(HTML 源代码)或 calendar(iCalendar .ics 格式)等子类型。一般来说,MIME 类型是分配给文件类型的名称,用于确定传输数据的内容类型,以及基于该类型的应用程序的相应行为。根据 MIME 类型,我们可以确定文件类型,那么如何从一个文件中识别其 MIME 类型呢?要确定 MIME 类型,我们需要读取其内容。每种文件类型都会有不同的存储方式,比如 ZIP 文件的文件规范就像这里一样。但仍有一些共同特征可用于识别。 文件签名是存储在文件开头的模式字节(也称为神奇数字或神奇字节),用于识别文件的内容和格式。下表列出了一些常用格式的文件签名(在此查看一些文件签名)。

除了使用文件签名外,有时还需要读取文件内容来找到确切的文件类型。例如,SVG 格式本质上是 XML。因此,要确定它,除了需要读取魔法数字来确定 XML 格式外,还需要读取里面更多的内容,才能正确确定 SVG 格式。

其他一些格式,如 Apple iWork,实际上是 Zip 文件中 XML 文件的集合。此时,Zip 文件负责制作包含 XML 文件的容器。由于需要解压其中的内容,文件类型识别变得更加困难。

使用 Apache Tika

     确定 MIME 类型 在 Java 系统中,可以使用 Apache Tika 提取信息并确定文件数据的确切格式。Apache Tika 可根据以下几个标准确定文件的数据格式:

魔数(Magic number)

文件名扩展名(File name extension):部分基于文件扩展名

从互联网下载文件的元数据(Metadata)

定义容器及其内容(container)

要在 Maven 项目中使用 Tika,可以在 pom.xml 中添加依赖关系:

<dependency>
     <groupId>org.apache.tika</groupId>
     <artifactId>tika-core</artifactId>
     <version>2.1.0</version>
</dependency>

因此,我们可以编写更多的函数,在文件上传到系统时确定文件的正确 MIME 类型。

public class FileUtils {
   public static String getRealMimeType(MultipartFile file) {
     AutoDetectParser parser = new AutoDetectParser();
     Detector detector = parser.getDetector();
     try {
       Metadata metadata = new Metadata();
       TikaInputStream stream = TikaInputStream.get(file.getInputStream());
       MediaType mediaType = detector.detect(stream, metadata);
       return mediaType.toString();
     } catch (IOException e) {
       return MimeTypes.OCTET_STREAM;
     }
   }
}

在后台再创建一个 API,并使用 Tika 来识别 MIME 类型。

@PostMapping(path = "/check-real-type", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity < Response > checkRealType(@RequestPart MultipartFile file) {
   String mimeType = FileUtils.getRealMimeType(file);
   return ResponseEntity.ok(new Response(mimeType));
}

然后,编辑用户界面,使用新创建的 API 将文件上传到后台,并用一些文件再次进行测试:

    在文件 real.png 中,Tika 识别出了正确的 MIME 类型。在 fake.zip 文件中,尽管改名为 fake.zip,Tika 仍能正确识别出文件的原始 MIME 类型为 image/jpeg。

Tika 支持的格式列表请点击此处


.netCore可以使用

https://www.nuget.org/packages/Mime-Detective
https://github.com/KevM/tikaondotnet
https://github.com/nissl-lab/toxy

Golang 可以使用

https://github.com/gabriel-vasile/mimetype
https://github.com/golang/go/blob/master/src/mime/type.go


小结

     后端系统在接收上传文件时应验证上传文件的类型。根据浏览器检测到的 MIME 类型来检查文件类型可能还不够,因为在某些情况下,文件会被改成扩展名,以便对系统进行钓鱼。每种文件类型都有不同的结构。借助 Java 系统上的 Apache Tika,可以根据文件格式确定文件的确切类型。

总结安全相关关注点

用户和文件系统权限

  1. 只允许授权用户上传文件
  2. 只允许系统用户读取文件(当文件公开时不适用) ,考虑使用 JWT 来确保应用程序的安全。

上传和下载限制

  1. 设置文件大小限制
  2. 设置适当的请求限制

只要在应用程序属性文件中添加这两个属性,就能轻松实现这一目标:

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

扩展和类型验证

  1. 允许扩展的白名单。只允许安全和重要的扩展,并确保应用输入验证。
  2. 这样,用户在选择上传图片时就会受到限制。

    # Example for Bootstrap-Vue
    <
    b-form-file ...
    :accept="
    .pdf,.png
    "
    />

  3. 在后台也要验证文件类型!不要相信 Content-Type 标头,而是从文件名中提取扩展名(使用适当的 regex 或库,如 Apache Commons IO),或者考虑使用 MIME-Type 检测。然后,将其与有效类型进行比较,如果无效则显示错误。
# Using Apache Commons IO. 
Returns "pdf"
import org.apache.commons.io.FilenameUtils;
String extension = FilenameUtils.getExtension("test.pdf");

     3.设置正确的 Content-Type 类型,从文件中提取,而不是使用 Content-Type 标头。考虑使用 Apache Tika。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.detect.Detector;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;public static String getContentType(byte[] fileBytes, String filenameWithExtension) throws IOException {
TikaConfig config = TikaConfig.getDefaultConfig();
Detector detector = config.getDetector();
TikaInputStream stream = TikaInputStream.get(new ByteArrayInputStream(fileBytes));
Metadata metadata = new Metadata();
metadata.add(Metadata.RESOURCE_NAME_KEY, filenameWithExtension);
return detector.detect(stream, metadata).toString();
}
文件名消毒 
  1. 将文件名改为由应用程序生成的文件名
  2. 设置文件名长度限制
  3. 尽可能限制允许的字符数
# Always has length 36 and is limited to 23 charsimport java.util.UUID;
public static String createUniqueFilename(String extension) {
return UUID.randomUUID().toString() + "." extension;
}
文件内容验证 
  1. 扫描文件以查找病毒。检查 Java 病毒检测服务或 Java Dockerized 病毒检测服务
  2. 应用图像重写技术,摧毁图像中注入的任何恶意内容。详情请查看
  3. 转换为固定格式。考虑使用 ImageMagick MVNRepository

文件存储位置

  1. 将文件存储在不同的主机上。允许在为用户提供服务的应用程序与处理文件上传及其存储的主机之间实现完全的职责分离。有关更多信息,请查看资源。
  2. 将文件存储在 web root 之外,只允许管理员访问。如需公开访问文件,可使用应用程序内映射到文件名的处理程序(someid -> file.ext)

参考

今天先到这儿,希望对云原生,技术领导力, 企业管理,系统架构设计与评估,团队管理, 项目管理, 产品管理,信息安全,团队建设 有参考作用 , 您可能感兴趣的文章:
构建创业公司突击小团队
国际化环境下系统架构演化
微服务架构设计
视频直播平台的系统架构演化
微服务与Docker介绍
Docker与CI持续集成/CD
互联网电商购物车架构演变案例
互联网业务场景下消息队列架构
互联网高效研发团队管理演进之一
消息系统架构设计演进
互联网电商搜索架构演化之一
企业信息化与软件工程的迷思
企业项目化管理介绍
软件项目成功之要素
人际沟通风格介绍一
精益IT组织与分享式领导
学习型组织与企业
企业创新文化与等级观念
组织目标与个人目标
初创公司人才招聘与管理
人才公司环境与企业文化
企业文化、团队文化与知识共享
高效能的团队建设
项目管理沟通计划
构建高效的研发与自动化运维
某大型电商云平台实践
互联网数据库架构设计思路
IT基础架构规划方案一(网络系统规划)
餐饮行业解决方案之客户分析流程
餐饮行业解决方案之采购战略制定与实施流程
餐饮行业解决方案之业务设计流程
供应链需求调研CheckList
企业应用之性能实时度量系统演变

如有想了解更多软件设计与架构, 系统IT,企业信息化, 团队管理 资讯,请关注我的微信订阅号:

作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 该文章也同时发布在我的独立博客中-Petter Liu Blog。

热门相关:鬼喊抓鬼   弃妇当家:带着萌宝去种田   全民女神:重生腹黑千金   大周仙吏   误踩老公底线:甜心难招架!