在 我对这个网站的联系表单的威胁模型 中,我发现了四个我在自己的设计中发现的弱点。找到问题是容易的部分。更难的部分是决定 先修复什么,如何修复,什么不修复。
这篇文章就是那个第二部分:一个小的、真正的补救计划。

我如何对它们进行排名
比较风险的一种简单方法是对每个风险提出两个问题:
- 可能性: 这个弱点被利用或造成伤害的可能性有多大?
- 影响: 如果它发生了,会有多糟糕,会影响谁?
上面的矩阵是我自己的判断,而不是测量结果。这是正常的:大多数真正的风险评估都是基于判断的,并写下来以便他人可以挑战它。价值在于使推理可见。
1. 保留所有消息:首先修复
风险。 通过表单发送的每条消息都会无限期地保留在数据库中。每条消息都包含一个名称、一个电子邮件地址和发送者选择告诉我的任何内容。如果数据库被泄露,无论是通过泄露的凭证还是我自己的错误,所有发送的内容都会随之泄露。
为什么它排名第一。 影响的是 其他人,他们信任我并提供了他们的详细信息。并且,某个数据存储在多年后最终被泄露的可能性并不是很小。您不保留的数据就不会泄露。
修复。 一个计划任务,删除超过固定期限的消息。Cloudflare Workers 支持 cron 触发器,因此接收消息的同一个 Worker 可以运行每日 DELETE FROM messages WHERE created_at < … 查询。十二个月感觉是正确的:足够长,以便跟进对话,足够短,以限制泄露可能揭示的内容。这也使网站更接近加纳数据保护法 2012 年(第 843 号法案)中的存储限制原则。
2. 'unsafe-inline' 在脚本策略中
风险。 网站的内容安全策略允许内联脚本。CSP 是一种防止跨站脚本攻击(XSS)的安全网:如果攻击者曾经成功地将脚本注入页面,严格的策略将阻止其运行。有 'unsafe-inline',安全网在错误的地方有一个漏洞。
Why it ranks second. The likelihood is low: the site is static and doesn’t display user-submitted content, so there is no obvious injection point today. But the impact of a successful XSS on every visitor would be high, and defence in depth means not relying on “there’s no bug today”.
解决方案。 用每个内联脚本的 哈希值 替换 'unsafe-inline'。在构建时,计算每个内联脚本的 SHA-256 哈希值,并在策略中列出这些哈希值。然后浏览器仅运行我批准的脚本。由于该网站是静态的,因此哈希值仅在我更改脚本时更改,因此可以在构建中自动执行。
3. 可以被竞争的速率限制
风险。 每小时每个 IP 地址的五条消息限制存储在 Cloudflare KV 中,这是最终一致的,代码读取计数器然后将其写为两个单独的步骤。并行发送的一组请求可以全部读取相同的旧计数并略微超过限制。
为什么它排名第三。 它很容易触发,但影响很小:几个额外的垃圾邮件仍然必须通过 Turnstile 机器人检查。对于联系表格,近似限制是可以接受的。对于诸如登录尝试之类的东西,它 不 可接受。
解决方案。 将计数器移动到具有原子更新的东西:Cloudflare的内置速率限制或Durable Object,它一次处理一个给定键的请求。两者都可以消除竞争。
4. 共享IP共享一个限制:接受,记录,监控
风险。 许多移动网络将大量用户放在少量共享的公共IP地址后面。在这样的网络上,一个滥用用户可能会耗尽无辜用户的每小时限制。
为什么我接受它。 每个替代方案都有成本。限制电子邮件地址很容易通过虚假地址来规避。删除IP限制会削弱对抗洪水的保护。大量提高限制会让更多的垃圾邮件通过。考虑到被阻止的访客可以稍后再试或直接通过电子邮件联系我,伤害很小。
“接受”意味着什么。 接受风险是一个合理的决定,但只有当它是有意识和记录的时才是如此。所以我在这里写下来,包括理由,如果我看到真正的访客被阻止,我会重新审视它。如果这是一个面向成千上万的移动用户的服务,而不是一个个人联系表单,我会将其排名更高。
这次练习教会了我什么
两个教训:
- 并非每个发现都很紧急。 根据可能性和影响进行排名,阻止我在真正保护人们(删除旧数据)之前花时间在最有趣的技术解决方案(速率限制竞争)上。
- “修复”,“缓解”和“接受”都是有效的结果,只要选择是故意的。声称可以修复一切的安全计划通常是没有仔细检查的计划。
我将在每个修复发布时更新此帖子。
不同意我的排名? 我想听听你的理由.
