フォームデータの送受信に関するセキュリティ対策まとめ

今回、webサービスのセキュリティについて考える機会があったので記事にします。
前提・目的は、「formタグでPOSTする機能を持ったwebサービスの保護」です。ログイン機能は実装済みで、サーバセッション&Cookieで管理するものとします。
前半で考えられる攻撃について述べ、後半で対策の実装を考察しています。

※この記事はあくまで個人の意見なので必ず他のソースも調べる等して、鵜吞みにしないでください。
 この情報が原因で発生したいかなる問題や損失も補償しません。自己責任でお願いします。
 これとは別にXSS等他にも対策は必要です。

CSRFについて

formのセキュリティ対策について調べるとまず出てくるのがCSRFでしょう。
これはサーバ側が本来拒否するべき他サーバからのリクエストを通してしまうことが原因です。
対策としてCSRFトークンを実装すれば良いというものがセオリーです。

<input type="hidden" name="token" value="XXXaaabbbCCCddd...">

サーバ側で生成したトークンをセッションに保存しておき、かつformの中にもhiddenとしてセットします。
POSTを受け取った際にセッション内のトークンとPOSTで受け取ったトークンを比較することで、正規のリクエストかどうかを判定するものです。

CSRFトークンの勘違い

これはあくまで「段階を踏んだリクエストかどうか」を判断できるものであって、単体で「正規のリクエスト」かどうかを判断出来るものではありません。攻撃者がGETでトークンを取得してからPOST時にセットすればリクエスト自体は通ります。なので単体では他のサイトからの攻撃は防げないのです。

クリックジャッキング

続いてクリックジャッキングについて。
iframeタグを使うと、他サイトを入れ子のように表示することができます。この時点でcssを使って意図しない操作をユーザにさせてしまうことは想像に難くないですし、
この入れ子サイトにはjsでアクセスすることも可能だったりとやりたい放題です。
そして問題なのはCookieが生きていること。なのでこの攻撃もサーバからすれば正しいリクエストに見えてしまうのです。Cookieを送信しないように出来ないの?と思うでしょうが、それはJS側の設定次第なのでどうしようもありません。

身分詐称・JSの書き換え

URLや各画面のHTMLを見ることで、例えば他のユーザのIDを知ることは意外と可能だったりします。設計方針次第ではありますが、このような情報は基本的に攻撃者に漏れてしまうと考えていたほうが良いです。では、ログイン済みの攻撃者がF12開発者ツールを使ってJSを書き換え、好き勝手な情報を送ってきた場合にはどうしましょう?

対策

必ず実装して欲しいものが、
・CSRFトークン
・iframeを許可しない(最近はデフォルトで対策済み?)
・CORS制約
・POST受信時、更新前に必ず権限確認
です。
上記に共通して生じるリスクは「不正な処理でもCookieは生きている」こと。
全て正しいログイン情報、セッション情報を持ったリクエストが飛んできます。
以下解説。

まず、CSRFトークンによって、最低限段階は踏んだアクセスであることは担保されます。しかし攻撃者がGETをしてしまうと、formのパラメータは丸わかりです。そこで有効なのがCORS制約。これによって外部ドメインからのリクエストについては、「処理は実行されるがレスポンスはブラウザ側で破棄される」という状態になります。
悪質サイトを訪問しているユーザのブラウザがトークンの入ったレスポンスを破棄するため、攻撃者のサイトはトークンを取得することが不可能なのです。
逆に言えばCORS制約が正しく機能していない場合は非常にリスキーだと言えます。正しく設定できているか今一度確認しましょう。ともかくCORSと認証Cookieのおかげで、正規のリクエストであることは担保されました。

続いて、iframe対策として.htaccessに以下を追加します。

Header set X-FRAME-OPTIONS "DENY"

コードで設定することも可能ですが、.htaccessに追加することで前ページに適用されます。この記述によってiframeでの表示は全て拒否することが可能です。これにより悪質サイトの中で私たちのサイトが操作されるようなことはなくなりました。
上記までは外部の悪質なサイトに対する対策です。

そして、ディベロッパーツールによるJS直接書き換え等はどう対応するべきか?これもjsの書き換えを防ぐことは不可能なので、サーバ側や機能設計で回避するようにしましょう。
まず、攻撃者もログインしているということは、サーバ側に攻撃者のIDが存在し、これが正しいことは確約されています。なのでこの情報を基に、各データに対する権限を持っているかどうか。これを都度確認する処理設計にしましょう。
例えばユーザ情報の更新機能にて、ユーザIDはPOSTで受け取った値ではなくセッションのユーザIDを使って更新するというような感じ。もしこのセッション情報を基に権限確認ができていれば、他のユーザに被害が及ぶような処理を実行することはできないはずです。
ブラウザから受け取る情報はほぼ全て改ざん可能であるということを頭に置いておきましょう。

まとめ

正解はないと思って、必ず自分自身で納得のいくまで調べましょう。
また、フレームワークを使っているから大丈夫!なんて言えないことが分かったかと思います。必ずフレームワークを使っている場合でもセキュリティ関係はどうなっているのか、調べてください。