Web 上线前的检查清单
- 安全性:
- 发送邮件
- SEO
- OGP
安全性
与认证相关的 Cookie 属性
设置了 HttpOnly 属性
减轻了 XSS 攻击
HttpOnly 表示只能通过 HTTP(S) 发送到服务器,而不能被 JavaScript 通过 document.cookie 的方式获取到。这样 XSS 就算获取了 JavaScript 能力,也无法获取到用户的这部分 cookie。
SameSite 属性设置为 Lax 或 Strict
主要为了防止 CSRF 攻击。在 Lax 的情况下,还需要同时检查是否有通过 GET 请求执行更新操作的端点
设置了 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
参考:X-Content-Type-Options: nosniff 不仅仅是 IE 需要
其它安全性
在注销/更改电子邮件地址等需要特别防御的部分,要求必须重新登录
防范 XSS 或会话劫持造成的损害
确保用户可变响应内容没有被缓存到 CDN 或 KVS 中
确保对象存储的目录页面 URL 没有公开
防止通过图片 URL 追踪到其他图片 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
检查 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
经常被分享的页面已完成 OGP 的设置
想要设置的几个关键点:
- og:title
- og:description
- og:url
- og:image
- twitter:card (X 的卡片格式)
添加支付功能时
已确认如何处理结算流程的负责人
支付失败时,应用内的数据与 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 等工具进行检查