译:Web 服务公开前的检查清单

原文:https://zenn.dev/catnose99/articles/547cbf57e5ad28
作者:catnose
译者:ChatGPT 4 Turbo

编者注:Fav 数很高的一篇文章,认真读应该能有少收获。

我个人制作了一份「Web 服务发布前的检查清单」,现已相当完善,因此决定公开。这份清单整理了我在过去犯错时,以及在信息收集过程中觉得「明天可能轮到我」时记下的个人笔记。

请事先了解,本清单可能存在遗漏或偏差,且根据服务的要求,可能有不适用的条目。

安全性

与认证相关的 Cookie 属性

  • 设置了 HttpOnly 属性
    • 减轻 XSS 攻击
    • 编者注:为啥?HttpOnly 表示只能通过 HTTP(S) 发送到服务器,而不能被 JavaScript 通过 document.cookie 的方式获取到。这样 XSS 就算获取了 JavaScript 能力,也无法获取到用户的这部分 cookie。
  • SameSite 属性设置为 LaxStrict
    • 主要为了防止 CSRF 攻击。在 Lax 的情况下,还需要同时检查是否有通过 GET 请求执行更新操作的端点
  • 设置了 Secure 属性
    • 仅通过 HTTPS 通信传输 Cookie
  • Domain 属性已适当设置
    • 如果 Cookie 设置为也发送到子域名,则需要了解如果其他子域站点存在漏洞,可能会导致安全事件的风险
    • 例如:example.com 的 Cookie 也发送到了招聘站点的 jobs.example.com,而该服务器存在漏洞等
    • 参考:Cookie 的 Domain 属性最安全的设置是不指定 *
    • 将 Cookie 名的前缀设置为 __Host- 可以使不为空的 Domain 属性的 Cookie 被忽略(参考:Cookie Prefix 的绕过

用户输入值的验证

  • 验证不仅在客户端进行,而且在服务器端也进行
  • 对用户输入的 URL 进行适当验证
  • 接收到的 HTML 被直接输出的部分已排除了传递危险字符串的可能性
    • element.innerHtml = input 或 React 的 dangerouslySetInnerHtml 等部分
    • 显示用户输入值时,事先进行逃逸或清理
  • 不存在可能导致 SQL 注入的 SQL 语句
  • 如果用户可以指定 URL 中包含的用户名等,那么进行了适当的验证
    • 用户指定的用户名被设置到类似 https://example.com/◯◯ 的情况需要注意
    • 拒绝与应用程序正在使用的路径可能重叠的字符串
    • 拒绝以下划线开头的字符串(托管在某些云服务上时,可能已经保留。例如:Google App Engine 保留了 /_ah
    • 拒绝仅由数字组成的字符串(根据路径的构成,框架可能会自动将对 /404 的请求处理为 404,这在某些情况下可能不是问题)
    • 设置保留字符串列表,防止用户注册。例如 admincontact 等(参考:reserved-usernames

响应头

  • 指定了 Strict-Transport-Security 响应头

    • 指示浏览器在指定时间内,对于指定的域名只能通过 HTTPS 而不是 HTTP 连接

      {
        key: 'Strict-Transport-Security',
        value: 'max-age=31536000; includeSubDomains; preload'
      }
      
  • 每个页面的响应头中都提供了 X-Frame-Options: "DENY"X-Frame-Options: "SAMEORIGIN"(补充:建议设置 CSP 的 frame-ancestors

    • 防止不期望的其他网站通过 iframe 等方式嵌入页面 = 防止点击劫持
  • 指定了 X-Content-Type-Options: nosniff

其他安全性

  • 在注销/更改电子邮件地址等需要特别防御的部分,要求必须重新登录
    • 防范 XSS 或会话劫持造成的损害
  • 确保用户可变响应内容没有被缓存到 CDN 或 KVS 中
  • 确保对象存储的目录页面 URL 没有公开
  • 对客户端传递的 URL 进行验证再重定向(防止开放重定向攻击)
    • 例如:不应该让用户点击 https://example.com/login?redirect_to=https://evil.example 后重定向到 https://evil.example
  • 在更新/删除操作中,未经认证的用户或无权限的用户不能更新数据
  • 在 SQL 的 DELETE 或 UPDATE 语句中 WHERE 条件被正确设置
    • 例如:本意是批量更新某个用户的 Product,结果却让所有 Product 都被更新。在我的项目中,除了特定查询以外,我通过 Extensions 设置禁用了 Prisma 的 updateMany/deleteMany
  • 用户提供的字符串没有直接包含在响应头中
    • 响应头被篡改的风险
  • 服务器发生的错误消息没有直接显示在浏览器上
  • 在文件上传功能中对文件格式、大小、文件名等进行了验证
  • 数据库的定期备份已启用
  • 对象存储的备份已启用
  • 使用的云服务账户已启用双因素认证
  • (根据服务的要求设置 CSP

登录

  • 需要确认电子邮件地址已经过本人验证
    • 即使是从 ID 提供商那里获得的电子邮件地址,也需验证其是否进行了本人确认
  • 不能列举已注册的电子邮件地址
    • 通过登录画面或密码重置画面的“该电子邮件地址未注册”等错误信息,第三方不应能查明电子邮件地址是否已被注册
    • 例如,Firebase Authentication 在这方面有漏洞,但似乎在 2023 年得到了解决
  • 如果提供多种登录方式,当相同用户注册账户时,应明确规定其规范,并反映在实现中
    • 例:用户已通过电子邮件 + 密码认证注册账户,之后使用同一电子邮件的 Google 账户登录时的处理方式
  • 允许更改电子邮件地址和关联账户

发送邮件

  • 如果用户的输入值包含在邮件中,不应利用这些输入发送垃圾邮件
    • 例:如果用户名或物品标题包含宣传或垃圾邮件内容,通过编辑这些内容便可发送垃圾邮件等
  • 用户执行特定操作时,应避免向大量用户重复发送邮件
    • 例:如果使用了关注功能关注了一万人,则会向一万人发送通知邮件等
  • 已完成 SPF / DKIM / DMARC 的设置
  • 如果通过批处理发送邮件,即使连续调用处理程序,也应避免邮件重复发送
    • 例如,在 AWS 或 Google Cloud 等服务中,可能会存在「At least once delivery」的情况
  • 对于新闻通讯或营销邮件,应能在未登录的情况下取消订阅
  • 向大量用户发送营销邮件时,应支持 List-Unsubscribe=One-Click

SEO

  • 所有页面都应正确指定 title 标签
  • 对 SEO 至关重要的页面应设定 canonical URL
    • 例:确保搜索引擎能识别 https://example.com/products/foohttps://example.com/products/foo?query=bar 是相同的内容
  • 错误相关页面的状态码应为 40x 或 50x,或设置为 noindex
  • 搜索结果页面应设置 noindex 或 canonical URL
    • 或者,在 <title> 标签和 <h1> 标签的内容中明确包含“搜索结果:◯◯”。否则,可能会有奇怪的关键字被索引进搜索结果
    • 例如:如果 https://example.com/search?keyword=UNKO 的页面标题是“UNKO”,那么“UNKO”可能会被索引进搜索结果
  • 确保整个站点未被设置为 noindex
    • “等到发布时再移除”很容易被忘记
  • 确保高搜索流量页面(如首页)设置了 meta description
    • 个人认为对于用户生成的页面等无需强制设置,宁愿不设置也不要设置奇怪的 meta description。

OGP

  • 经常被分享的页面已完成 OGP 的设置
    • 想要设置的几个关键点

添加支付功能时

  • 已确认如何处理结算流程的负责人
  • 支付失败时,应用内的数据与 Stripe 等支付服务商的数据不会产生不一致
    • 万一发生这种情况,是否能够被检测到
    • 例如:在 Stripe 上支付成功,但数据库更新失败等
  • 实现了避免重复支付的措施
  • 即使之前进行过支付的用户注销了账户,也不会对财务或应用逻辑产生不一致
  • 订阅中的用户退订(或账号被冻结)时,能够自动取消订阅
  • 用户退订时是否退款或按日计费的规定写在了使用条款中
    • 在退订页面上写明这些细节也是个好主意
  • 准备了取消订阅的引导流程
  • 如果因信用卡过期等原因导致订阅更新失败,能够处理这种情况,并且用户可以轻松找到更新支付信息的途径
  • 收据满足合格发票要求(发票制度)

可访问性

  • 图像(<img>)的 alt 属性被适当地指定

  • 仅含 svg 图标的 <button><a> 能够被屏幕阅读器正确识别其作用

    <a href="/" aria-label="显示链接作用的文本">
      <svg aria-hidden="true" ... ></svg>
    </a>
    

这两点很容易被忽略,因此加入了检查清单。关于其他项目,freee 可访问性指南会有帮助。

性能

  • 确保打包的 JS 中不含有无用模块
    • 发布前用 bundle-analyzer 等工具检查是个好方法
  • 静态文件被缓存到 CDN 上
    • 使用 Next.js 或 Nuxt.js 等框架时,会有大量的 js/css 文件请求,这些静态文件最好通过 CDN 分发
  • 避免因图片造成的布局偏移
    • 在 img 元素上指定 CSS 的 aspect-ratio 或 width/height 属性
  • 没有加载过大的图片
  • 示例:宽度为 400px 的图片实际大小却是 2MB。这种情况很常见
  • 确保适当地添加了 SQL 的索引
    • 在发布后数据增加时进行对策似乎也不错

在多个环境中确认操作

  • 在手机或平板尺寸的屏幕上显示时 UI 没有崩溃
    • 这个问题非常常见
  • 在不同的操作系统上查看时字体没有出现问题
    • 确保 font-family 在 Mac、Windows、iOS、Android、(Linux) 等操作系统上的设置不会显得不自然
  • (如果开发环境是 Mac)在系统环境设置中把「始终显示滚动条」选项打开也不会出现问题
    • 在设置为始终显示滚动条的情况下,打开模态框等时可能会出现抖动。可能需要采用 scrollbar-gutter 进行相应的处理
  • 用户指定的昵称等输入值过长时界面不会崩溃

其他

  • 本地存储或非 http-only 的 Cookie 等在 7 天后消失也没问题
    • 虽然不太为人所知,但最近的 iOS Safari 中由于 ITP 的规范,如果用户 7 天以上没有操作,通过浏览器上的 JavaScript 保存的 Cookie 或本地存储的内容将自动删除(参考
  • 不依赖第三方 Cookie
  • 如果是日文网站,确保为 <html lang="ja">
    • 需要注意的是,一些框架的默认设置可能是 lang="en"
  • 当发生服务器错误时,错误内容被通知或可以被检测到
  • 404 页或 50x 系列页面的表现不错
    • 在 404 的情况下,应该显示指向首页等的链接,引导进行下一步操作
  • 设置了网站图标(Favicon)
  • 设置了 apple-touch-icon
  • 安装了 Google Analytics 等访问分析工具(如果需要)
  • 如果提供封闭聊天等服务,已向电信业务申请参考
  • 服务名称在其他语言中没有不恰当的含义
    • 可以询问 ChatGPT,或使用 WordSense 等工具进行检查