✅
Webサービス公開前のチェックリスト
個人的に「Webサービスの公開前チェックリスト」を作っていたのですが、けっこう育ってきたので公開します。このリストは、過去に自分がミスしたときや、情報収集する中で「明日は我が身…」と思ったときなどに個人的にメモしてきたものをまとめた内容になります。
セキュリティ
認証に関わるCookieの属性
-
HttpOnly属性が設定されていること
- XSSの緩和策
-
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のバリデーションが適切に行われていること
- protocolの制限も忘れずに。
javascript:
のようなURLを指定できないようにする - 正規表現を使う場合、バイパスできないかチェックする(文頭チェック漏れや、Rubyのマルチラインフラグのバイパスなど)
- protocolの制限も忘れずに。
-
受け取ったHTMLをそのまま出力する部分において、危険な文字列が渡される可能性が排除されていること
-
element.innerHtml = input
やReactのdangerousllySetInnerHtml
のような部分 - ユーザーの入力値を表示する際には事前にエスケープやサニタイズを行うこと
-
- SQLインジェクションが起こりうるSQL文が存在しないこと
-
ユーザーがURLに含まれるハンドルネーム等を指定できる場合、バリデーションが適切におこなわれていること
- ユーザー指定のハンドルネームが
https://example.com/◯◯
にあてられる場合は要注意 - アプリケーションで使用しているパスと被りうる文字列は拒否する
- アンダースコアから始まる文字列は拒否する(ホスティング先のクラウドサービスがリザーブしている場合がある。 例: Google App Engineだと
/_ah
はリザーブされている) - 数字だけの文字列は拒否する(
/404
へのリクエストをフレームワークが自動的に404にすることがある。パスの構成によっては問題ない) - リザーブドの文字列一覧を設定し、ユーザーが登録できないようにしておく。
admin
やcontact
など(参考: reserved-usernames)
- ユーザー指定のハンドルネームが
レスポンスヘッダ
-
Strict-Transport-Security
がレスポンスヘッダに指定されていること- ブラウザに指定された期間中、指定されたドメインへの接続は常にHTTPではなくHTTPSを使用するように指示
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' }
- ブラウザに指定された期間中、指定されたドメインへの接続は常にHTTPではなくHTTPSを使用するように指示
-
各ページのレスポンスヘッダに(追記: CSPのX-Frame-Options: "DENY"
もしくはX-Frame-Options: "SAMEORIGIN"
が付与されていること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が適切に設定されていること
- 例: とあるUserのProductを一括更新するつもりが、全Productが更新されてしまう等。自分のプロジェクトではPrismaの
updateMany
/deleteMany
を特定のクエリ以外では使えないようにExtensionsで設定している。
- 例: とあるUserのProductを一括更新するつもりが、全Productが更新されてしまう等。自分のプロジェクトではPrismaの
-
ユーザーから渡された文字列をそのままレスポンスヘッダに含めていないこと
- レスポンスヘッダが改竄される可能性
- サーバーで発生したエラーメッセージをそのままブラウザに表示していないこと
- ファイルのアップロード機能において、ファイル形式やサイズ、ファイル名などのバリデーションが行われていること
- DBの定期的なバックアップが有効になっていること
- オブジェクトストレージのバックアップが有効になっていること
- 利用するクラウドサービスのアカウントで二要素認証が有効になっていること
- (サービスの要件次第でCSPの設定)
ログイン
-
メールアドレスの本人確認が行われていること
- IDプロバイダから渡されたメールアドレスであっても本人確認ができているか検証すること
-
登録されているメールアドレスの列挙ができないこと
- ログイン画面やパスワード再設定画面で「このメールアドレスは登録されていません」といったエラーメッセージから第三者が登録されているかどうかを調べられてしまう
- 例えばFirebase Authenticationはこれができてしまう仕様だったが2023年に対応された模様
-
複数のログイン方法を提供している場合、同一ユーザーがアカウント登録したときの仕様が決まっており、実装に反映されていること
- 例: メールアドレス + パスワード認証でアカウント登録したユーザーが、同一メールアドレスのGoogleアカウントでログインした場合にどうなるか
- メールアドレス変更や、連携アカウントの変更ができること
メール送信
-
ユーザーの入力値がメールに含まれる場合、その入力を悪用してスパムメールが送られないこと
- 例: ユーザー名やアイテムのタイトルに宣伝やスパム的な内容を含めると、その内容を編集することでスパムにできてしまう等
-
ユーザーが特定の操作をしたときに不特定多数に繰り返しメールが送られないこと
- 例: フォロー機能で1万人をフォローすると1万人に通知メールが送られる等
- 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が設定されるくらいなら無くていい。
- 動的にパブリックなページが生成される場合、XMLサイトマップを作成し、Search Consoleに登録されていること
OGP
-
頻繁にシェアされることが想定されるページのOGPの設定が完了していること
- このあたりを設定しておきたい
- og:title
- og:description
- og:url
- og:image
- twitter:card (Xのカードの形式)
- このあたりを設定しておきたい
決済機能をつける場合
- どのように会計処理を行うか担当者と確認が取れていること
-
決済に失敗したときに、アプリケーション上のデータと、Stripe等の決済代行サービス上のデータで不整合が生じないこと
- 万が一生じた場合には、そのことを検知できるようになっているか
- 例: Stripe上で決済が成功したが、DBの更新処理に失敗する等
- 重複決済が発生しない実装になっていること
- 決済を行ったことがあるユーザーが退会しても会計やアプリケーションのロジックに不整合が生じないこと
- サブスクリプションを契約中のユーザーがサービスを退会(もしくはアカウント凍結)したときに、サブスクリプションが自動キャンセルされること
-
決済を行ったことがあるユーザーが退会したときの返金有無や日割り計算について利用規約に書かれていること
- 退会ページにもこのあたりの注意点は書いておくのが良い
- 解約の導線が用意されていること
- カードの期限切れ等によりサブスクリプションの更新が失敗したときのハンドリングができていること。また、ユーザーに支払い情報更新の導線が分かりやすく表示されること
-
領収書が適格請求書の要件を満たしていること(インボイス制度)
- 適格請求書発行事業者の場合。Stripeなら日本での請求書/インボイスを設定する際のベストプラクティスが参考になる
アクセシビリティ
-
画像(
<img>
)のalt属性が適切に指定されていること- altの指定の仕方は情報バリアフリーポータルサイトが参考になる
-
内部がsvgアイコンだけの
<button>
や<a>
の役割がスクリーンリーダーからも認識できるようになっていること<a href="/" aria-label="リンクの役割を示すテキスト"> <svg aria-hidden="true" ... ></svg> </a>
この2つは忘れがちなのでチェックリストに入れました。その他の項目ついてはfreeeアクセシビリティー・ガイドラインが参考になります。
パフォーマンス
-
余計なモジュールがバンドルJSに含まれていないこと
- リリース前にbundle-analyzerなどで確認するのが良い
-
静的ファイルがCDNにキャッシュされていること
- Next.jsやNuxt.jsなどのフレームワークでは大量のjs/cssファイルにリクエストが飛ぶが、これらの静的ファイルはCDNから配信したい
-
画像によるレイアウトシフトが起きないこと
- img要素にCSSのaspect-ratio もしくは width/height属性を指定する
-
必要以上に巨大な画像が読み込まれていないこと
- 例: 幅400pxで小さく表示されている画像のサイズが実は2MBだった。よく見かける
-
SQLのインデックスが適切に貼られていること
- リリース後データが増えてから対策を行っても良さそう
複数環境での動作確認
-
スマホやタブレットサイズの画面で表示したときにUIが崩れないこと
- これめっちゃよく見る
-
各OSで見たときにフォントがおかしなことになっていないこと
- font-familyがMac、Windows、iOS、Android、(Linux)など各OSでも不自然にならない指定になっていること
-
(開発環境がMacの場合)システム環境設定で「スクロールバーを常に表示」にしても問題がないこと
- スクロールバーが常に表示される設定では、モーダルを開くとき等にガタつきがち。scrollbar-gutterによる対応が必要になるかも
- ユーザーが指定したハンドルネームなどの入力値が長いときに見た目が崩れないこと
その他
-
ローカルストレージやhttp-onlyでないCookie等が7日で消えても問題がないこと
- あまり知られていないが、最近のiOS SafariではITPの仕様により、ブラウザ上のJavaScriptから保存されたCookieやローカルストレージの内容は7日以上ユーザーが触らないと自動で削除される(参考)
- サードパーティCookieに依存していないこと
-
日本語サイトの場合、
<html lang="ja">
となっていること- フレームワークによってはデフォルトで
lang="en"
となっていることがあるので注意
- フレームワークによってはデフォルトで
- サーバーエラーが発生したときにエラーの内容が通知される or 検知できるようになっていること
-
404ページや50x系ページが悪くない感じになっていること
- 404の場合はトップページ等へのリンク等、次のアクションへの導線が表示されていること
- ファビコンが設定されていること
- apple-touch-iconが設置されていること
- Google Analyticsなどアクセス解析ツールを導入していること(必要なら)
- クローズドチャットなどのサービスを提供する場合、電気通信事業者の申請をしていること(参考)
-
サービス名が他言語でおかしな意味でないこと
- ChatGPTとかに聞くか、WordSenseとかを使うのが良い
「これも忘れがちだから入れた方がいいのでは?」という内容があればコメントなどで教えていただけると嬉しいです。
Discussion
凄く有益な記事をありがとうございます!
普段意識してはいるものの言語化できていないことが多いので、
こういったチェックシートは大変ありがたいです...!
XSSが発生した際には
credentials:include
で叩かれるので無意味だ!なんて話も聞きますが、攻撃者が認証情報を手にして手元の端末から好き勝手叩かれるようなことは無くなるので緩和策にはなるんですよね。
重要な情報へのアクセスにはパスワードを必須化するといった設計の話も組み込まれておりますし、要点が箇条書きでまとまっており大変読みやすいです。
是非いろんな人に見てもらいたいですね。
ありがとうございます。まだ欠けているポイントが多くありそうなので、気付き次第加筆修正しようと思います。
すごい勉強になりました!
有益な記事の執筆ありがとうございます。勉強になりました!
Cookieの設定が不適切な場合、ブラウザ側でCookieを拒否して安全性を高める仕様があります。
クッキー名のプレフィックスに
__Secure-
をつけると、Secure属性を持たないCookieはブラウザで拒否されます。クッキー名のプレフィックスに
__Host-
をつけると、Secure属性・Path属性==/・Domain属性無しを満たさないクッキーはブラウザで拒否されます。すでにご存じでしたら、すいません。
有益な情報をありがとうございます。記事本文にも追記しておきます!
とても参考になる記事ありがとうございます!
私の場合、こちらにサブスクリプション決済が有効期限切れなどでエラーが出た際に、アプリケーション側でしっかり管理できているかを追加しますね!
何度か失敗しているものでして・・・!
ハマりやすいところですよね。追記しておきます!
有益なリストをありがとうございます!
パフォーマンスのところでSQLでN+1問題が発生していないかもよくチェックします。
(パスワードでログインがある場合)
/.well-known/change-password
を実装するとかどうでしょう/.well-known/security.txt
と合わせて検討すると良さそうですね!質問
利用ライブラリの脆弱性(例: npm auditの結果がhigh, criticalなど)もこの記事の観点に含まれますか?