原文: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 属性设置为
Lax
或Strict
- 主要为了防止 CSRF 攻击。在
Lax
的情况下,还需要同时检查是否有通过 GET 请求执行更新操作的端点
- 主要为了防止 CSRF 攻击。在
- 设置了 Secure 属性
- 仅通过 HTTPS 通信传输 Cookie
- Domain 属性已适当设置
- 如果 Cookie 设置为也发送到子域名,则需要了解如果其他子域站点存在漏洞,可能会导致安全事件的风险
- 例如:
example.com
的 Cookie 也发送到了招聘站点的jobs.example.com
,而该服务器存在漏洞等 - 参考:Cookie 的 Domain 属性最安全的设置是不指定
*
- 将 Cookie 名的前缀设置为
__Host-
可以使不为空的 Domain 属性的 Cookie 被忽略(参考:Cookie Prefix 的绕过)
用户输入值的验证
- 验证不仅在客户端进行,而且在服务器端也进行
- 对用户输入的 URL 进行适当验证
- 不要忘了限制协议。禁止指定例如
javascript:
这样的 URL - 使用正则表达式时,检查是否存在绕过的可能(忽略文首检查或 Ruby 的多行标志的绕过等)
- 不要忘了限制协议。禁止指定例如
- 接收到的 HTML 被直接输出的部分已排除了传递危险字符串的可能性
- 如
element.innerHtml = input
或 React 的dangerouslySetInnerHtml
等部分 - 显示用户输入值时,事先进行逃逸或清理
- 如
- 不存在可能导致 SQL 注入的 SQL 语句
- 如果用户可以指定 URL 中包含的用户名等,那么进行了适当的验证
- 用户指定的用户名被设置到类似
https://example.com/◯◯
的情况需要注意 - 拒绝与应用程序正在使用的路径可能重叠的字符串
- 拒绝以下划线开头的字符串(托管在某些云服务上时,可能已经保留。例如:Google App Engine 保留了
/_ah
) - 拒绝仅由数字组成的字符串(根据路径的构成,框架可能会自动将对
/404
的请求处理为 404,这在某些情况下可能不是问题) - 设置保留字符串列表,防止用户注册。例如
admin
或contact
等(参考: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 追踪到其他图片 URL。根据服务要求,这可能没问题,但进行设置更安全
- 参考:Cloud Storage 中索引页面应设为非公开
- 对客户端传递的 URL 进行验证再重定向(防止开放重定向攻击)
- 例如:不应该让用户点击
https://example.com/login?redirect_to=https://evil.example
后重定向到https://evil.example
- 例如:不应该让用户点击
- 在更新/删除操作中,未经认证的用户或无权限的用户不能更新数据
- 在 SQL 的 DELETE 或 UPDATE 语句中 WHERE 条件被正确设置
- 例如:本意是批量更新某个用户的 Product,结果却让所有 Product 都被更新。在我的项目中,除了特定查询以外,我通过 Extensions 设置禁用了 Prisma 的
updateMany
/deleteMany
。
- 例如:本意是批量更新某个用户的 Product,结果却让所有 Product 都被更新。在我的项目中,除了特定查询以外,我通过 Extensions 设置禁用了 Prisma 的
- 用户提供的字符串没有直接包含在响应头中
- 响应头被篡改的风险
- 服务器发生的错误消息没有直接显示在浏览器上
- 在文件上传功能中对文件格式、大小、文件名等进行了验证
- 数据库的定期备份已启用
- 对象存储的备份已启用
- 使用的云服务账户已启用双因素认证
- (根据服务的要求设置 CSP)
登录
- 需要确认电子邮件地址已经过本人验证
- 即使是从 ID 提供商那里获得的电子邮件地址,也需验证其是否进行了本人确认
- 不能列举已注册的电子邮件地址
- 通过登录画面或密码重置画面的“该电子邮件地址未注册”等错误信息,第三方不应能查明电子邮件地址是否已被注册
- 例如,Firebase Authentication 在这方面有漏洞,但似乎在 2023 年得到了解决
- 如果提供多种登录方式,当相同用户注册账户时,应明确规定其规范,并反映在实现中
- 例:用户已通过电子邮件 + 密码认证注册账户,之后使用同一电子邮件的 Google 账户登录时的处理方式
- 允许更改电子邮件地址和关联账户
发送邮件
- 如果用户的输入值包含在邮件中,不应利用这些输入发送垃圾邮件
- 例:如果用户名或物品标题包含宣传或垃圾邮件内容,通过编辑这些内容便可发送垃圾邮件等
- 用户执行特定操作时,应避免向大量用户重复发送邮件
- 例:如果使用了关注功能关注了一万人,则会向一万人发送通知邮件等
- 已完成 SPF / DKIM / DMARC 的设置
- 如果通过批处理发送邮件,即使连续调用处理程序,也应避免邮件重复发送
- 例如,在 AWS 或 Google Cloud 等服务中,可能会存在「At least once delivery」的情况
- 对于新闻通讯或营销邮件,应能在未登录的情况下取消订阅
- 向大量用户发送营销邮件时,应支持 List-Unsubscribe=One-Click
- 检查 Gmail 的邮件发送者指南 是个不错的选择
SEO
- 所有页面都应正确指定 title 标签
- 对 SEO 至关重要的页面应设定 canonical URL
- 例:确保搜索引擎能识别
https://example.com/products/foo
和https://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
添加支付功能时
- 已确认如何处理结算流程的负责人
- 支付失败时,应用内的数据与 Stripe 等支付服务商的数据不会产生不一致
- 万一发生这种情况,是否能够被检测到
- 例如:在 Stripe 上支付成功,但数据库更新失败等
- 实现了避免重复支付的措施
- 即使之前进行过支付的用户注销了账户,也不会对财务或应用逻辑产生不一致
- 订阅中的用户退订(或账号被冻结)时,能够自动取消订阅
- 用户退订时是否退款或按日计费的规定写在了使用条款中
- 在退订页面上写明这些细节也是个好主意
- 准备了取消订阅的引导流程
- 如果因信用卡过期等原因导致订阅更新失败,能够处理这种情况,并且用户可以轻松找到更新支付信息的途径
- 收据满足合格发票要求(发票制度)
- 如是合格发票发行者。使用 Stripe 的话,在日本设置发票/发票的最佳实践会有所帮助
可访问性
-
图像(
<img>
)的 alt 属性被适当地指定- 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 等工具进行检查