KoderKoder.ai
价格企业教育投资人
登录开始使用

产品

价格企业投资人

资源

联系我们支持教育博客

法律信息

隐私政策使用条款安全可接受使用政策举报滥用

社交

LinkedInTwitter
Koder.ai
语言

© 2026 Koder.ai 保留所有权利。

首页›博客›安全的文件上传:权限、限制、签名 URL 与扫描
2025年11月24日·1 分钟

安全的文件上传:权限、限制、签名 URL 与扫描

在 Web 应用中实现安全文件上传需要严格的权限、大小与速率限制、签名 URL 和简单的恶意文件扫描模式以避免事故。

为什么文件上传存在风险(通俗说明)

文件上传看起来无害:头像、一份 PDF、一个表格。但它们常常是第一次安全事件的起点,因为它们允许陌生人把一个“神秘包裹”交给你的系统。如果你接受它、存储它,并把它展示给其他人,就为攻击你的应用创造了新途径。

风险不仅仅是“有人上传了病毒”。一个恶意的上传可能泄露私有文件、导致存储费用暴增,或欺骗用户交出访问权限。名为 “invoice.pdf” 的文件可能根本不是 PDF。即便是真正的 PDF 或图片,也会在你的应用信任元数据、自动生成预览或用错误规则提供时引发问题。

真实的失败通常像这样:

  • 有人猜到文件 URL 并下载到另一个用户的文档。
  • 上传的 HTML 文件被当作网页服务,展示了窃取登录信息的提示。
  • 攻击者反复上传超大文件,直到你的应用变慢或崩溃。
  • 一个“安全”的文件类型被伪造,然后被内部人员在机器上打开。

一个细节导致了许多事故:存储文件不是等同于提供文件。存储是你保留字节的地方。服务是这些字节如何被传递给浏览器和应用。当应用以与主站相同的信任级别和规则来服务用户上传时,问题就会出现,浏览器会把该上传当作“受信任”的内容。

对于一个小型或发展中的应用,“足够安全”通常意味着你能在不模棱两可的情况下回答四个问题:谁可以上传、你接受什么、大小和频率是多少、以及以后谁能读取。即便你在快速构建(用生成代码或聊天驱动平台),这些护栏仍然很重要。

针对上传的简单威胁模型

把每个上传都当作不受信任的输入。保持上传安全的实际方法是想象谁可能滥用它们,以及对方的“成功”是什么样。\n\n大多数攻击者要么是扫弱点的机器人,要么是想通过上传来获取免费存储、爬取数据或捣乱的真实用户。有时也会是竞争对手在探测泄露或中断。

他们想要什么?通常是以下几种结果之一:

  • 通过上传可执行内容在你的服务器上运行代码。
  • 通过猜测、重用或分享下载 URL 来窃取私有文件。
  • 通过洪水式上传或强制昂贵处理来破坏可用性。
  • 通过存储增长或大带宽下载来抬高你的账单。

然后将薄弱点映射出来。上传端点是前门(超大文件、奇怪格式、高请求率)。存储是后室(公开的 bucket、错误权限、共享文件夹)。下载 URL 是出口(可预测、长期有效或未绑定到特定用户)。

示例:一个“简历上传”功能。机器人上传了数千个大 PDF 来制造费用,而滥用用户上传了 HTML 文件并以“文档”分享以欺骗他人。

在添加控制之前,先决定对你的应用最重要的是什么:隐私(谁能读)、可用性(能否持续提供)、成本(存储与带宽)和合规(数据存放位置及保留时长)。这个优先级列表能让决策保持一致。

切实可靠的权限与访问控制

大多数上传事故并非复杂黑客攻击,而是“我可以看到别人的文件”这类简单错误。把权限当作上传的一部分,而不是事后补上的功能。

从一句话开始:默认拒绝。假定每个上传对象都是私有的,除非你明确允许。对发票、医疗文件、账户文档和任何与用户相关的内容,采用“默认私有”是稳健的基线。只有在用户明确期望公开(比如公开头像)时才把文件设为公开,即便如此也要考虑时限访问。

与实际工作匹配的角色划分

保持角色简单且分离。一种常见划分是:

  • Uploader:可以为其账户创建上传
  • Viewer:可以下载被允许查看的文件
  • Support:只在可审计的临时授权下访问文件
  • Admin:管理策略,但不应自动读取所有内容

不要依赖诸如“/user-uploads/ 下的都安全”这类基于文件夹的规则。在每次读取时核验所有权或租户访问权限。这能在人员换岗、离职或文件被重新分配时保护你。

一个好的支持模式是窄且临时:授予对单个文件的访问、记录日志并自动过期。

在不信任的前提下校验文件类型

大多数上传攻击始于一个简单伎俩:文件看起来安全(靠文件名或浏览器头部),但实际上并非如此。把客户端发送的一切都当作不可信。

从允许列表开始:决定你接受的确切格式(例如 .jpg、.png、.pdf),拒绝其他所有格式。除非确实需要,否则避免允许“任何图片”或“任何文档”。

不要信任文件名后缀或客户端的 Content-Type 头。这两者都容易伪造。名为 invoice.pdf 的文件可能是可执行文件,Content-Type: image/png 也可能是假的。

更可靠的方法是检查文件的前若干字节,通常称为“magic bytes” 或文件签名。许多常见格式都有一致的头部(如 PNG、JPEG)。如果头部与允许的格式不符,就拒绝上传。

一个实用的验证设置:

  • 服务端维持允许的扩展名列表。
  • 在服务器上检测 MIME 类型(而不是信任客户端头)。
  • 对支持的格式做 magic bytes 嗅探。
  • 生成新的随机存储名,并把原始名称作为元数据保存。
  • 阻止高风险格式,除非确实需要,尤其是 HTML、SVG 和脚本类内容。

重命名比看起来更重要。如果你直接存储用户提供的名字,会引入路径技巧、奇怪字符和意外覆盖的风险。使用生成的 ID 作为存储键,把原始文件名仅作为展示元数据。

对头像,只接受 JPEG 和 PNG,验证头部并尽可能去除元数据。对文档,考虑仅限 PDF,并拒绝任何带有活动内容的文件。如果你后来确实需要支持 SVG 或 HTML,把它们视为可能可执行并加以隔离。

大小限制、速率限制与 DoS 基础

大多数上传导致的中断并非“高级黑客手段”,而是超大文件、过多请求或慢速连接占用服务器导致应用无法响应。把每个字节都当成成本。

在实际场景中设定大小上限

为每个功能挑选最大值,而不是一个全局数值。头像不需要与税务文档或短视频相同的限制。设定看起来合适的最小限制,当确实需要时再提供独立的“大文件上传”路径。

在多个地方强制执行限制,因为客户端可能撒谎:在应用逻辑中、在 Web 服务器或反向代理上、通过上传超时机制,并在声明大小过大时及早拒绝(在读取完整请求体前)。

具体示例:头像限制为 2 MB,PDF 限制为 20 MB,任何更大的文件走不同流程(如使用带签名 URL 的直连对象存储)。

速率限制与滥用控制

即便是小文件,如果有人反复上传也会成为 DoS。对上传端点按用户和按 IP 添加速率限制。对匿名流量采用更严格的限制。\n\n可续传上传对网络不佳的真实用户有帮助,但会话令牌必须严格:短期过期、与用户绑定,并与特定文件大小和目的地绑定。否则“续传”端点会变成通往存储的免费通道。

当你阻止上传时,返回对用户友好的错误(文件过大、请求过多),但不要泄露内部信息(堆栈跟踪、bucket 名称或供应商细节)。

安全的存储与交付选择

Keep data where it belongs
Choose where your app runs to match privacy and cross-border requirements.
Get Started

安全的上传不仅关乎你接受什么,也关乎文件存放在哪里以及以后如何交付。

把上传的字节从主数据库中分离。大多数应用只需要在数据库中保存元数据(所有者用户 ID、原始文件名、检测到的类型、大小、校验和、存储键、创建时间)。把字节放到对象存储或专为大二进制设计的文件服务中。

在存储层面区分公开与私有文件。使用不同的 buckets/容器并设置不同规则。公开文件(如公开头像)可以无需登录直接读取。私有文件(合同、发票、医疗文档)绝不应公开可读,即便有人猜到 URL。

尽量避免从与你的主域相同的域提供用户文件。如果一个高风险文件(HTML、包含脚本的 SVG 或浏览器的 MIME 嗅探异常)滑过检查,在主域托管可能导致帐号接管。独立的下载域(或存储域)能限制影响范围。

在下载时,强制使用安全头。基于你允许的类型设置可预测的 Content-Type,不要信任用户声明的类型。对于任何可能被浏览器解释的内容,优先以下载方式发送。

一些能防止意外的默认设置:

  • 对文档使用 Content-Disposition: attachment。
  • 使用安全的 Content-Type(或 application/octet-stream)。
  • 使用不透明的对象键存储和服务(而非用户文件名)。
  • 记录私有文件的下载日志。

保留策略也是安全的一部分。删除被弃置的上传、替换后移除旧版本并为临时文件设置时限。存储的数据越少,泄露的风险越低。

签名 URL:什么时候使用以及如何收紧它们

签名 URL(常称为预签名 URL)是一个常见方式,允许用户在不将存储桶设为公开、也不通过你的 API 传输每个字节的情况下上传或下载文件。URL 携带临时权限,之后失效。

两种常见流程:

  • 直连存储上传:你的应用签发短期签名 URL,浏览器直接向对象存储上传。
  • 通过服务器上传:文件先到你的 API,然后你的服务器把它保存到存储中。

直连存储能降低 API 负载,但这会使存储规则和 URL 约束更为重要。

如何保持签名 URL 的严格性

把签名 URL 当作一次性密钥。使其具体且短期有效。

  • 写入型 URL 快速过期(通常 1–5 分钟)。阅读型 URL 也应以分钟为单位,而非几天。
  • 将 URL 绑定到你期望的具体对象键(单个对象,而非文件夹)。
  • 在支持时添加约束:期望的 Content-Type、最大大小、校验和。
  • 仅在权限校验通过后签发 URL。
  • 记录谁请求了 URL 及其原因(用户 ID、对象键、用途、IP/UA)。

一个实用模式是在签发 URL 前先创建上传记录(状态:pending),上传后确认对象存在且符合预期大小与类型,然后再标记为可用。

可实现的逐步安全上传流程

Rollback risky changes
Use snapshots and rollback when tightening upload rules during fast iteration.
Try Snapshots

一个安全的上传流程主要是清晰的规则和明确的状态。把每个上传都当作在检查完成前不可信。

把每个功能允许的内容写下来:头像和税务文档不应共享同样的文件类型、大小限制或可见性。

实用流程(带实际状态)

  1. 定义允许的类型和每个功能的大小上限(例如:照片最多 5 MB,PDF 最多 20 MB)。在后端强制相同规则。

  2. 在字节到达前创建“上传记录”。存储:所有者(用户或组织)、用途(avatar、invoice、attachment)、原始文件名、预期最大大小,以及 pending 状态等。

  3. 上传到私有位置。不要让客户端选择最终路径。

  4. 在服务器端再次验证:大小、magic bytes/类型、允许列表。如果通过,把状态改为 uploaded。

  5. 进行恶意软件扫描并把状态更新为 clean 或 quarantined。如果扫描是异步的,在等待期间保持访问锁定。

  6. 只有当状态为 clean 时才允许下载、预览或处理。

小示例:对于头像,创建与用户绑定且用途为 avatar 的记录,私有存储,确认它是真正的 JPEG/PNG(而不是仅仅叫这个名字),扫描后生成预览 URL。

基本的恶意软件扫描模式(不过不要过度承诺)

恶意软件扫描是安全网,不是保证。它能拦截已知的恶意文件和明显的伎俩,但检测不到所有问题。目标是简单:降低风险并把未知文件默认设置为无害。

可靠的模式是先隔离。把每个新上传先保存到私有且非公开的位置并标记为 pending。只有通过检查后才把它移动到“clean”位置或标记为可用。

同步扫描只适用于小文件和低流量,因为用户需要等待。大多数应用采用异步扫描:接受上传,返回“处理中”状态,后台完成扫描。

“基本扫描”通常包括的内容

基本扫描通常包含杀毒引擎(或服务)加上一些护栏:AV 扫描、文件类型检查(magic bytes)、归档限制(zip 炸弹、嵌套 zip、巨大解压后大小),以及阻止不需要的格式。

如果扫描失败、超时或返回“未知”,把文件视为可疑。将其隔离并不要提供下载链接。团队常犯的错是把“扫描失败”当作“也发吧”。

当你阻止一个文件时,给出的信息要中性:“我们无法接受此文件。请尝试其他文件或联系客服。”除非你很有把握,否则不要声称你检测到了恶意软件。

示例:典型应用中的头像和文档上传

考虑两个功能:一个公开展示的头像和一个用于账单或支持的 PDF 收据(私有)。两者都是上传问题,但不应共享同样的规则。

对头像保持严格:只允许 JPEG/PNG,限制大小(例如 2–5 MB),并在服务器端重新编码,这样就不会直接提供用户的原始字节。检查通过后再存为公开存储。

对 PDF 收据,允许更大尺寸(例如最多 20 MB),默认私有,并避免从主域内联渲染它们。

一个简单的状态模型能在不暴露内部细节的情况下告知用户:

  • pending:用户已选择文件,尚未开始上传
  • uploaded:存储已接收字节
  • scanning:后台作业正在检查
  • clean(或 rejected):文件可用或被阻止

签名 URL 很适合这种流程:使用短期写入签名 URL 进行上传(写入权限、绑定单一对象键)。仅在状态为 clean 时签发短期读取签名 URL。

记录你需要的调查信息,而不是文件本身:用户 ID、文件 ID、类型猜测、大小、存储键、时间戳、扫描结果、请求 ID。避免记录原始内容或文件内发现的敏感数据。

常见错误与容易掉进的陷阱

Deploy with less hassle
Go from chat to hosted app, then iterate on upload security in minutes.
Deploy App

大多数上传漏洞来自一个临时捷径变成永久设置。假定每个文件都不受信任、每个 URL 都会被分享、每个“我们稍后会修”的设置都会被忘记。

反复出现的问题包括:

  • 仅依赖客户端检查。浏览器可以在几秒钟内被绕过。
  • 让用户影响路径、文件名或对象键。
  • 把文件“暂时”设为公开。
  • 使用存活时间过长或可为多个用户使用的签名 URL。
  • 用错误的 Content-Type 提供文件,让浏览器去解释风险内容。

监控是团队最常跳过的环节,直到存储费用飙升。监控上传量、平均大小、主要上传者和错误率。一个被攻破的账号就能在一夜之间悄悄上传数千个大文件。

示例:团队在共享文件夹下使用用户提供的文件名存储头像,如 “avatar.png”。某个用户覆盖了别人的图片。解决办法很乏味但有效:在服务器端生成对象键、默认私有存储,并通过受控响应暴露经缩放的图片。

快速检查清单与下一步

在发布前把这当作最后检查清单。把每项当作发布阻断项,因为多数事故来自缺少某个护栏。

快速检查清单

  • 在服务器端用允许列表和真实内容检查(而非仅文件名)以及每个文件的硬性最大限制进行验证。
  • 默认私有存储,并在每次读取、下载或预览时核验权限。
  • 如果使用签名 URL 上传,确保它们短期生效、绑定到单个对象键并记录签发以便追溯滥用。
  • 先隔离后扫描:在文件被标记为 clean 之前不要生成预览或允许下载。
  • 强制安全的下载行为:可预测的 Content-Type、安全的文件名以及对文档使用 attachment。

值得投入的下一步

用通俗语言把规则写下来:允许的类型、最大大小、谁能访问、签名 URL 的生存期,以及“扫描通过”意味着什么。这将成为产品、工程和支持之间的共享契约。

添加一些能捕捉常见失败的测试:超大文件、改名的可执行文件、未授权读取、过期签名 URL 和“扫描待定”的下载。这些测试比一次事故花费要低得多。

如果你在快速构建与迭代,使用一种可以安全规划变更并回滚的工作流会很有帮助。使用 Koder.ai (koder.ai) 的团队通常在收紧上传规则时依赖 planning mode 和快照/回滚,但核心要求不变:策略应由后端强制执行,而不是由 UI 决定。

常见问题

What’s the minimum I should do to make file uploads “safe enough”?“

开始时先做到默认私有,并把每个上传都当作不受信任的输入。在服务器端执行四项基本检查:

  • 谁可以上传
  • 接受哪些文件类型(允许列表)
  • 大小/频率限制(大小 + 速率限制)
  • 谁以后可以读取(逐文件的权限检查)

如果你能清楚回答这些问题,就比大多数团队更安全了。

Why are file uploads a common first security incident?

因为用户可以上传一个“黑匣子”,你的应用可能存储并在之后把它展示给其他用户。这可能导致:

  • 未经授权访问私有文档
  • 如果文件被当作受信任的网页内容展示,可能引发钓鱼或帐号接管
  • 上传洪水或超大文件导致服务中断或账单暴涨

通常问题不只是“有人上传了病毒”。

What’s the difference between storing files and serving files, and why does it matter?

存储是把字节放在某处;服务是把字节交付给浏览器或应用。\n\n危险在于,当应用以与主站相同的信任级别和规则来对待用户上传时,浏览器可能把上传当作“受信任”的页面去执行或展示。\n\n更安全的默认做法是:先私有存储,再通过受控的下载响应并使用安全头来交付。

How do I stop users from downloading someone else’s uploaded file?

采用默认拒绝,并在每次下载或预览时都做权限校验。\n\n实用规则:\n\n- 每个文件记录应有所有者(用户/组织)和用途(avatar、invoice 等)\n- 在读/下载时,验证请求者是否有该文件的访问权限\n- 避免基于文件夹的简单规则(如“/uploads/ 下的都可以”)\n- 支持人员访问应为临时且有日志(只授予单个文件,自动过期)\n\n大多数漏洞其实就是“我能看到别人的文件”这类简单错误。

How do I validate file type without trusting the filename or Content-Type?

不要信任文件名后缀或浏览器发来的 Content-Type。在服务器端验证:\n\n- 为每个功能使用允许列表(例如头像只允许 JPEG/PNG,收据只允许 PDF)\n- 在服务器端检测类型并检查magic bytes(文件签名)\n- 存储时用随机 ID 重命名;原始文件名只做展示元数据\n- 阻止不需要的高风险格式(尤其是 HTML、SVG、脚本类内容)\n\n如果字节流与允许的格式不符,就拒绝上传。

What limits should I set to prevent upload-based DoS attacks?

常见的中断来源是无趣的滥用:太多上传、超大文件或慢速连接占用服务器资源。\n\n一些适用的默认设置:\n\n- 为不同功能设定最大值(头像小,文档大)\n- 在多个层级强制执行(应用 + 反向代理 + 超时)\n- 对每个用户和每个 IP 设定速率限制,匿名流量限制更严格\n\n把每个字节都当成成本,每个请求都可能是滥用。

Should I use signed URLs for uploads, and what’s the safest default?

可以,但必须谨慎。签名 URL 让浏览器直接与对象存储交互,避免把存储设为公开。\n\n良好默认:\n\n- 写入型 URL 短期生效(通常 1–5 分钟)\n- 每个 URL 绑定到一个对象键,而不是整个文件夹\n- 只在权限校验通过后签发 URL\n- 记录谁为哪个文件请求了 URL\n\n直连存储能减少 API 负载,但范围和过期策略不可妥协。

What’s a safe step-by-step upload flow I can implement?

最安全的模式是:\n\n1. 在字节到达前创建带状态 pending 的上传记录\n2. 将字节上传到私有位置\n3. 在服务器端再次验证大小 + 类型(magic bytes)\n4. 扫描(通常异步),把状态改为 clean 或 quarantined\n5. 只有在 clean 时才允许下载/预览\n\n这能防止“扫描失败”或“仍在处理”的文件被意外分享。

Do I really need malware scanning, and what does “basic scanning” look like?

扫描有用,但不是万能。把它当作安全网,而不是唯一控制手段。\n\n实用做法:\n\n- 先隔离:不在扫描完成前公开链接\n- 异步扫描以便扩展;向用户显示“处理中”的状态\n- 如果扫描失败或超时,把文件视为可疑并保持阻断\n- 如果允许归档文件,加入对 zip 炸弹和巨大解压后的大小的防护\n\n关键在于策略:未完成扫描不应变成“可用”。

How should I deliver uploaded files safely (headers, domains, downloads)?

以防止浏览器把文件当网页解释为目标来交付文件:\n\n良好默认:\n\n- 对文档设置 Content-Disposition: attachment\n- 使用服务器选定的安全 Content-Type(或 application/octet-stream)\n- URL 中使用不透明的存储键(而非用户文件名)\n- 如果可能,为用户内容使用独立的下载域\n\n这样可以降低上传文件变成钓鱼页面或脚本执行的风险。

目录
为什么文件上传存在风险(通俗说明)针对上传的简单威胁模型切实可靠的权限与访问控制在不信任的前提下校验文件类型大小限制、速率限制与 DoS 基础安全的存储与交付选择签名 URL:什么时候使用以及如何收紧它们可实现的逐步安全上传流程基本的恶意软件扫描模式(不过不要过度承诺)示例:典型应用中的头像和文档上传常见错误与容易掉进的陷阱快速检查清单与下一步常见问题
分享