跳到内容

文件上传备忘录

简介

文件上传正成为任何应用程序中越来越重要的组成部分,用户可以上传他们的照片、简历或展示他们正在进行的项目视频。应用程序应该能够抵御虚假和恶意文件,以确保应用程序和用户的安全。

简而言之,应遵循以下原则来实现安全的文件上传:

  • 列出允许的扩展名。只允许对业务功能安全且关键的扩展名。
  • 验证文件类型,不要相信Content-Type 头,因为它可能被伪造。
  • 将文件名更改为应用程序生成的名称。
  • 设置文件名长度限制。如果可能,限制允许的字符。
  • 设置文件大小限制。
  • 只允许授权用户上传文件。
  • 将文件存储在不同的服务器上。如果不可能,则存储在 Web 根目录之外。
    • 如果文件需要公共访问,请使用映射到应用程序内部文件名的处理程序(someid -> file.ext)。
  • 如果可用,通过防病毒软件或沙盒运行文件,以验证其不包含恶意数据。
  • 如果适用类型(PDF、DOCX 等),通过 CDR(内容解除武装与重建)运行文件。
  • 确保所使用的任何库都已安全配置并保持最新。
  • 保护文件上传免受CSRF攻击。

文件上传威胁

为了评估并确切知道要实施哪些控制措施,了解您面临的威胁对于保护您的资产至关重要。以下章节有望展示文件上传功能带来的风险。

恶意文件

攻击者出于恶意目的传输文件,例如:

  1. 利用文件解析器或处理模块中的漏洞(例如ImageTrick 漏洞XXE)。
  2. 将文件用于网络钓鱼(例如职业申请表)。
  3. 发送 ZIP 炸弹、XML 炸弹(又称亿万笑声攻击),或者仅仅是巨大的文件,以填满服务器存储空间,从而阻碍并损害服务器可用性。
  4. 覆盖系统上的现有文件。
  5. 客户端活动内容(XSS、CSRF 等),如果文件可公开检索,可能会危及其他用户。

公共文件检索

如果上传的文件可公开检索,则可能面临额外的威胁:

  1. 其他文件被公开披露。
  2. 通过请求大量文件发起 DoS 攻击。请求很小,但响应要大得多。
  3. 文件内容可能被视为非法、冒犯性或危险(例如个人数据、受版权保护的数据等),这将使您成为此类恶意文件的主机。

文件上传保护

验证用户内容没有万能的解决方案。实施深度防御方法是使上传过程更困难、更符合服务需求和要求的关键。实施多种技术是关键且推荐的,因为没有一种技术足以保护服务安全。

扩展名验证

确保在解码文件名之后进行验证,并设置适当的过滤器,以避免某些已知的绕过方法,例如:

  • 双重扩展名,例如 .jpg.php,这很容易绕过正则表达式 \.jpg
  • 空字节,例如 .php%00.jpg,其中 .jpg 被截断,.php 成为新的扩展名。
  • 未经充分测试和审查的通用不良正则表达式。除非您对此主题有足够的知识,否则请避免构建自己的逻辑。

请参阅输入验证备忘录以正确解析和处理扩展名。

允许的扩展名列表

确保只使用业务关键的扩展名,而不允许任何类型的非必需扩展名。例如,如果系统要求:

  • 图像上传,只允许一种商定的、符合业务要求的类型;
  • 简历上传,允许 docxpdf 扩展名。

根据应用程序的需求,确保使用危害最小风险最低的文件类型。

阻止扩展名

识别潜在有害的文件类型,并阻止您认为对您的服务有害的扩展名。

请注意,单独阻止特定扩展名是一种弱保护方法。无限制文件上传漏洞文章描述了攻击者如何尝试绕过此类检查。

Content-Type 验证

上传文件的 Content-Type 由用户提供,因此不能信任,因为它很容易伪造。尽管不应将其用于安全目的,但它可以提供快速检查,以防止用户无意中上传类型不正确的文件。

除了定义上传文件的扩展名外,还可以检查其 MIME 类型,以快速防止简单的文件上传攻击。

这最好通过允许列表方法完成;否则,也可以通过拒绝列表方法完成。

文件签名验证

结合Content-Type 验证,可以检查和验证文件的签名与预期接收的文件是否一致。

不应单独使用此方法,因为绕过它非常普遍且容易。

文件名安全

文件名可能通过使用不可接受的字符或特殊和受限制的文件名来危及系统。对于 Windows,请参阅以下MSDN 指南。有关不同文件系统及其处理文件的更广泛概述,请参阅维基百科的文件名页面

为了避免上述威胁,将文件名创建为随机字符串(例如生成 UUID/GUID)至关重要。如果业务需求要求使用文件名,则应对客户端(例如导致 XSS 和 CSRF 攻击的活动内容)和后端(例如特殊文件覆盖或创建)攻击向量进行适当的输入验证。应根据存储文件的系统考虑文件名长度限制,因为每个系统都有自己的文件名长度限制。如果需要用户文件名,请考虑实施以下措施:

  • 实施最大长度。
  • 将字符限制为特定的允许子集,例如字母数字字符、连字符、空格和句点。
    • 考虑告知用户可接受的文件名是什么。
    • 限制使用前导句点(隐藏文件)和连续句点(目录遍历)。
    • 限制使用前导连字符或空格,以使使用 shell 脚本处理文件更安全。
    • 如果不可能,则阻止可能危及存储和使用文件的框架和系统的危险字符。

文件内容验证

公共文件检索部分所述,文件内容可能包含恶意、不当或非法数据。

根据预期类型,可以应用特殊的文件内容验证:

  • 对于图像,应用图像重写技术会破坏注入图像的任何恶意内容;这可以通过随机化完成。
  • 对于 Microsoft 文档,使用Apache POI有助于验证上传的文档。
  • 不建议使用ZIP 文件,因为它们可以包含所有类型的文件,并且与它们相关的攻击向量众多。

文件上传服务应允许用户举报非法内容,并允许版权所有者举报滥用行为。

如果有足够的资源,应在沙盒环境中进行手动文件审查,然后才将文件公开发布。

为审查添加一些自动化功能可能会有所帮助,这是一个艰巨的过程,在使用前应进行充分研究。一些服务(例如 Virus Total)提供 API 来扫描文件是否包含已知的恶意文件哈希。一些框架可以检查和验证原始内容类型,并根据预定义的文件类型进行验证,例如在ASP.NET Drawing Library中。警惕公共服务的数据泄露威胁和信息收集。

文件存储位置

文件的存储位置必须根据安全和业务需求选择。以下几点按安全优先级设置,并具有包容性:

  1. 将文件存储在不同的主机上,这允许在服务用户的应用程序与处理文件上传及其存储的主机之间完全职责分离。
  2. 将文件存储在Web 根目录之外,只允许管理访问。
  3. 将文件存储在Web 根目录之内,并只设置为写入权限。- 如果需要读取访问权限,则必须设置适当的控制措施(例如内部 IP、授权用户等)。

以经过研究的方式将文件存储在数据库中是一种附加技术。这有时用于自动备份过程、非文件系统攻击和权限问题。反过来,这可能会导致性能问题(在某些情况下)、数据库及其备份的存储考虑,并可能导致 SQLi 攻击。仅当团队中有 DBA 并且此过程显示出比将文件存储在文件系统上有所改进时,才建议这样做。

有些文件在上传后会被通过电子邮件发送或处理,并且不会存储在服务器上。在对它们进行任何操作之前,务必执行本备忘录中讨论的安全措施。

用户权限

在访问任何文件上传服务之前,应对上传文件的用户进行两个级别的适当验证:

  • 认证级别
    • 用户应为注册用户或可识别用户,以便对其上传能力设置限制。
  • 授权级别
    • 用户应具有访问或修改文件的适当权限。

文件系统权限

根据最小权限原则设置文件权限。

文件应以确保以下方式存储:

  • 只有允许的系统用户才能读取文件。
  • 文件只设置所需的模式。
    • 如果需要执行,作为安全最佳实践,在运行文件之前扫描文件是必需的,以确保没有宏或隐藏脚本。

上传和下载限制

应用程序应为上传服务设置适当的大小限制,以保护文件存储容量。如果系统将提取或处理文件,则应在文件解压缩后并使用安全方法计算 zip 文件大小后考虑文件大小限制。有关更多信息,请参阅如何从 ZipInputStream 安全提取文件,这是 Java 处理 ZIP 文件的输入流。

如果可用,应用程序也应为下载服务设置适当的请求限制,以保护服务器免受 DoS 攻击。

Java 代码片段

Dominique 为 Java 中的某些文档类型编写的文档上传保护仓库。