Webサービス公開前のチェックリスト

2024/07/04に公開
5

個人的に「Webサービスの公開前チェックリスト」を作っていたのですが、けっこう育ってきたので公開します。このリストは、過去に自分がミスしたときや、情報収集する中で「明日は我が身…」と思ったときなどに個人的にメモしてきたものをまとめた内容になります。

なお、この記事では各項目の解説はあまり入れていません。2024年7月10日のClassmethod Odysseyで少し詳しく話そうと思っているので、よかったら聞きにきてください。オンラインなので無料です。

セキュリティ

認証に関わるCookieの属性

  • HttpOnly属性が設定されていること
    • XSSの緩和策
  • 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のバリデーションが適切に行われていること
  • 受け取ったHTMLをそのまま出力する部分において、危険な文字列が渡される可能性が排除されていること
    • element.innerHtml = inputやReactのdangerousllySetInnerHtmlのような部分
    • ユーザーの入力値を表示する際には事前にエスケープやサニタイズを行うこと
  • SQLインジェクションが起こりうるSQL文が存在しないこと
  • ユーザーがURLに含まれるハンドルネーム等を指定できる場合、バリデーションが適切におこなわれていること
    • ユーザー指定のハンドルネームがhttps://example.com/◯◯にあてられる場合は要注意
    • アプリケーションで使用しているパスと被りうる文字列は拒否する
    • アンダースコアから始まる文字列は拒否する(ホスティング先のクラウドサービスがリザーブしている場合がある。 例: Google App Engineだと/_ahはリザーブされている)
    • 数字だけの文字列は拒否する(/404へのリクエストをフレームワークが自動的に404にすることがある。パスの構成によっては問題ない)
    • リザーブドの文字列一覧を設定し、ユーザーが登録できないようにしておく。admincontactなど(参考: reserved-usernames

レスポンスヘッダ

  • Strict-Transport-Security がレスポンスヘッダに指定されていること
    • ブラウザに指定された期間中、指定されたドメインへの接続は常にHTTPではなくHTTPSを使用するように指示
      {
        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が適切に設定されていること
    • 例: とあるUserのProductを一括更新するつもりが、全Productが更新されてしまう等。自分のプロジェクトではPrismaのupdateMany/deleteManyを特定のクエリ以外では使えないようにExtensionsで設定している。
  • ユーザーから渡された文字列をそのままレスポンスヘッダに含めていないこと
    • レスポンスヘッダが改竄される可能性
  • サーバーで発生したエラーメッセージをそのままブラウザに表示していないこと
  • ファイルのアップロード機能において、ファイル形式やサイズ、ファイル名などのバリデーションが行われていること
  • DBの定期的なバックアップが有効になっていること
  • オブジェクトストレージのバックアップが有効になっていること
  • 利用するクラウドサービスのアカウントで二要素認証が有効になっていること
  • (サービスの要件次第でCSPの設定)

ログイン

  • メールアドレスの本人確認が行われていること
    • IDプロバイダから渡されたメールアドレスであっても本人確認ができているか検証すること
  • 登録されているメールアドレスの列挙ができないこと
    • ログイン画面やパスワード再設定画面で「このメールアドレスは登録されていません」といったエラーメッセージから第三者が登録されているかどうかを調べられてしまう
    • 例えばFirebase Authenticationはこれができてしまう仕様だったが2023年に対応された模様
  • 複数のログイン方法を提供している場合、同一ユーザーがアカウント登録したときの仕様が決まっており、実装に反映されていること
    • 例: メールアドレス + パスワード認証でアカウント登録したユーザーが、同一メールアドレスのGoogleアカウントでログインした場合にどうなるか
  • メールアドレス変更や、連携アカウントの変更ができること

メール送信

  • ユーザーの入力値がメールに含まれる場合、その入力を悪用してスパムメールが送られないこと
    • 例: ユーザー名やアイテムのタイトルに宣伝やスパム的な内容を含めると、その内容を編集することでスパムにできてしまう等
  • ユーザーが特定の操作をしたときに不特定多数に繰り返しメールが送られないこと
    • 例: フォロー機能で1万人をフォローすると1万人に通知メールが送られる等
  • 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が設定されるくらいなら無くていい。
  • 動的にパブリックなページが生成される場合、XMLサイトマップを作成し、Search Consoleに登録されていること

OGP

  • 頻繁にシェアされることが想定されるページのOGPの設定が完了していること
    • このあたりを設定しておきたい

決済機能をつける場合

  • どのように会計処理を行うか担当者と確認が取れていること
  • 決済に失敗したときに、アプリケーション上のデータと、Stripe等の決済代行サービス上のデータで不整合が生じないこと
    • 万が一生じた場合には、そのことを検知できるようになっているか
    • 例: Stripe上で決済が成功したが、DBの更新処理に失敗する等
  • 決済を行ったことがあるユーザーが退会しても会計やアプリケーションのロジックに不整合が生じないこと
  • サブスクリプションを契約中のユーザーがサービスを退会(もしくはアカウント凍結)したときに、サブスクリプションが自動キャンセルされること
  • 決済を行ったことがあるユーザーが退会したときの返金有無や日割り計算について利用規約に書かれていること
    • 退会ページにもこのあたりの注意点は書いておくのが良い
  • 解約の導線が用意されていること
  • 領収書が適格請求書の要件を満たしていること(インボイス制度)

アクセシビリティ

  • 画像(<img>)の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

takecchitakecchi

凄く有益な記事をありがとうございます!

普段意識してはいるものの言語化できていないことが多いので、
こういったチェックシートは大変ありがたいです...!

認証に関わるCookieの属性

XSSが発生した際にはcredentials:includeで叩かれるので無意味だ!なんて話も聞きますが、
攻撃者が認証情報を手にして手元の端末から好き勝手叩かれるようなことは無くなるので緩和策にはなるんですよね。

重要な情報へのアクセスにはパスワードを必須化するといった設計の話も組み込まれておりますし、要点が箇条書きでまとまっており大変読みやすいです。

是非いろんな人に見てもらいたいですね。

catnosecatnose

ありがとうございます。まだ欠けているポイントが多くありそうなので、気付き次第加筆修正しようと思います。

yokotasoyokotaso

有益な記事の執筆ありがとうございます。勉強になりました!

認証に関わるCookieの属性

Cookieの設定が不適切な場合、ブラウザ側でCookieを拒否して安全性を高める仕様があります。

https://asnokaze.hatenablog.com/entry/2024/05/07/002720#Cookie名プレフィックス-Cookie-Name-Prefixes

https://caniuse.com/?search=Cookie prefixes

Secure属性が設定されていること

クッキー名のプレフィックスに __Secure- をつけると、Secure属性を持たないCookieはブラウザで拒否されます。

Domain属性が適切に設定されていること

クッキー名のプレフィックスに __Host- をつけると、Secure属性・Path属性==/・Domain属性無しを満たさないクッキーはブラウザで拒否されます。

すでにご存じでしたら、すいません。

catnosecatnose

有益な情報をありがとうございます。記事本文にも追記しておきます!