Content Security Policyのform-actionは問い合わせの送信先を決めてから制限する
公開日: 執筆:佐藤
form-actionは、formを送信できるURLをContent Security Policyで制限するdirectiveです。問い合わせformの送信先を決めずに追加すると、必要なformまで送れなくなることがあります。反対に、送信先を広く許せば、制限を置く意味が薄れます。まず、どのformがどこへ送信し、誰が受け取り、送信後にどのpageを見せるかを確かめます。
Content Security Policy Level 2は、form-actionをprotected resourceがformを送信できるURLを制限するdirectiveとして定義しています。CSPはcontent injectionによる被害を小さくするための追加の防御であり、入力を検査し出力を適切に扱う代わりにはなりません。headerを一行加える作業と、問い合わせを安全に受ける仕組みを同じものと考えません。
formの画面と送信先は別のURLである
問い合わせpageがhttps://example.jp/contact/にあっても、formの送信先が同じURLとは限りません。WordPressのpluginが別のendpointへ送る、外部のform serviceへ送る、JavaScriptが送信先を切り替える、といった構成があります。画面に表示するURLだけを見てform-action 'self'を設定すると、実際には必要な送信を止めることがあります。
説明用の仮例として、会社siteに三つのformがある場合を考えます。一つ目は問い合わせformで、自社siteの/contact/complete/へ送ります。二つ目は資料請求formで、外部の受付serviceへ送ります。三つ目はsite内検索formで、同じhostの検索結果URLへ送ります。この三つは画面上ではいずれもformですが、送信先と受ける仕組みが違います。form-actionを作る前に、formごとにaction、method、送信後の画面、受信する担当を記録します。
form-action 'self'は、同じoriginへの送信だけを許す例です。問い合わせformが自社site内の処理へ送る構成なら候補になります。しかしsubdomainが違う、受付serviceが別domainにある、決済や予約の画面へ移る場合は、同じcompanyのserviceであっても'self'では足りません。見た目のdomain名が似ているかではなく、実際の送信先URLで判断します。
Content-Security-Policy: form-action 'self'
この例は、同じoriginへの送信だけを許す基本形です。すぐに本番へ適用するための定型文ではありません。siteが必要とする送信先を確かめ、対象browserで送信から完了まで試した後に使います。
送信先を広げる前に外部serviceの役割を確認する
外部のform service、予約system、決済serviceへ送る場合は、許可するhostを追加する必要があるかもしれません。そのときにhttps:のように広く許す前に、なぜ外部へ送るのかを整理します。問い合わせを受けるserviceなのか、決済を行うserviceなのか、広告の計測なのかで、扱うdataと確認すべき内容が変わります。
たとえば資料請求formがhttps://forms.example-service.jp/へ送るなら、formのaction、埋込みcode、送信後の完了画面、受信mailを確認します。外部serviceがformを表示しているだけで、送信は自社serverへ戻す構成もあります。埋込み先のURLと送信先のURLを同じだと決めつけません。
問い合わせで氏名、mail address、電話番号を受けるなら、外部serviceへ渡す項目と、site側で保管する項目を分けます。form-actionは、送信を許す先を制限する機能です。個人情報を必要最小限にする、受信先のaccountを管理する、送信内容を暗号化して扱う、といった作業を置き換えません。security headerを設定した人と、問い合わせを受ける人が別なら、どのserviceへ何を渡すかを共有します。
外部serviceのURLが変わる場合もあります。制作会社がformを入れ替え、旧serviceを止めた後にCSPだけが古いまま残ると、送信が失敗します。反対に、不要になったhostを許可したままにすれば、必要な範囲を絞れません。formを変える作業には、HTML、JavaScript、CSP、受信mail、完了pageを確認する作業を含めます。
Report-Onlyで送信の実態を先に見る
CSP Level 2は、Content-Security-Policy-Report-Only headerでpolicy違反を監視し、resourceの読込みを止めずに試す方法を定義しています。form-actionも同じ考え方で、いきなり遮断する前に、想定したpolicyで必要な送信が違反にならないかを確認します。
Content-Security-Policy-Report-Only: form-action 'self'
Report-Onlyを出しただけで確認が終わるわけではありません。問い合わせform、資料請求form、site内検索、予約、決済など、siteにある送信操作を実際に試します。test dataを使い、送信、完了画面、受信mail、管理画面の記録まで進みます。違反reportが届く構成なら、どのpageのどのformがどのURLへ送ろうとしたかを記録します。
ここで「問い合わせformが送れた」だけでは不十分です。送信buttonを押した後にerrorがないか、完了pageが正しいか、担当者へmailが届くか、同じ内容が二重に登録されないかを見ます。formの送信、画面の遷移、mail delivery、受付systemへの登録は別の処理です。どこまで確認したかを分けて残します。
Report-Onlyで問題が出たときに、reportを消して終わらせません。まず、送信が必要なformなのか、古いtemplateから残った不要なformなのかを調べます。必要なら許可するhostを最小限に追加します。不要ならHTML、plugin設定、JavaScriptを削除するか、正しい送信先へ直します。policyを緩めることが解決ではなく、どの送信が事業に必要かを決めることが先です。
server側の検査とCSPを混同しない
formのactionを制限しても、受信側が入力内容を無条件に信頼してよいわけではありません。利用者はHTMLを直接書き換えたり、別のHTTP requestを送ったりできます。受信するserver側では、必須項目、形式、送信元の検査、保存処理を行います。CSPはbrowserがpage内のform送信を扱うときの制限であり、server側の検査を置き換えません。
WordPressのpluginを使う場合も、CSPをthemeのheaderへ書けば終わるとは限りません。hostingの設定、web server、CDN、security pluginのどこがHTTP headerを出しているかで、設定する場所と反映するpageが変わります。複数のCSP headerが出ているときは、どれがどのpolicyを持つかを確認します。制作会社へ頼むなら、設定した場所、対象URL、policyの値、Report-Onlyからenforceへ切り替える時刻、戻す方法を受け取ります。
公開後にformを追加する人にも、送信先の確認を引き継ぎます。新しい予約formを埋め込み、CSPの許可先を変えずに公開すれば、読者は送れません。反対に、動かないからと全hostを許可すれば、policyの範囲が広がります。formの追加依頼には、送信先URL、個人情報の項目、完了画面、受信担当、CSPの変更要否を含めます。
最初の一手は、公開中のformを全部並べ、actionのURLと送信後に誰が受けるかを書き出すことです。その一覧を基にReport-Onlyでtestし、必要な送信先だけをform-actionへ残します。
関連記事

