低コストでできるだけセキュアなAjaxパスワード認証を考える
2017/08/30
このブログで何年か前にAjaxを用いたログイン認証を試みたことがあります。しかし、それらのページの情報はあまりにも古く、セキュリティについてもほとんど配慮していませんでした。そこで今回、Ajaxによる認証処理をよりモダンでセキュアなやり方でもう一度取り上げたいと思います。色々なサイトやWebサイトのセキュリティに関する書籍を参考にしながら進めていく予定です。
モダンなAjaxパスワード認証を考えるとき、自分が課題と考えているのが以下の点です。
- SSLの導入
- CSRF・XSS対策
- パスワードの保管方法
- Ajaxページにおけるセッション(状態)管理
SSLの導入
SSLはEV SSLなどのまともなのを導入しようものなら高額にならざるをえません。でも、たとえばレンタルサーバーならば、共有という形であるものの無料でSSLを導入できるところがほとんどです。それらを利用してもアドレスバーは緑色になりませんが、オレオレ証明書の場合のような警告のポップアップはでません。また、さくらインターネットで、最近ラピッドSSLというSSLプランが提供され、月額ではなく年額が1500円程度でSSLが利用できるということです。このようにSSLは品質を別にすれば低価格化の道をすすんでいると言えます。
実際のところ、SSLの暗号通信の仕組み自体はWebアプリケーションサイドのプログラミングで実装できなくもありません。 ただ、通信データが間違いなくリクエスト先のサイトのものであるかどうかは、インターネットの仕組み上、確認するすべがありません。このことによるセキュリティ上のリスクはARPスプーフィングに代表される中間者攻撃ですが、これらは高度な攻撃方法であり、銀行やECサイト、 またはFacebookのように高度な個人情報を扱うサイトでもないかぎり、極度に警戒するほどでもない気がします。しかし、仮に自前で暗号化通信を行っていると宣伝したとしても、公的な手法でないことへ不安感を持たれたり、前述のように自前ではドメインなりサーバーの認証が不可能ですから、被害を受ける可能性は低いものの中間者攻撃に無力である点でセキュリティに穴があると見なされるでしょう。
このようにSSLの低価格化がすすんでいること、そして自前で通信のセキュリティを高めることの限界を考慮すると、やはり安価なものでもSSLを導入した方がよいという話しになってくるでしょう。自分のサイトはロリポップのレンタルサーバーなので、このプロジェクトでは共有SSLを使用していく考えです。この共有SSLは現在、ホスト名で各ユーザーに振り分けられる形になっています。元のURLがwiz-code.digick.jpならば、digick-wiz-code.ssl-lolipop.jpという具合になります。数年前まで、ロリポップはパス名で区分した領域をユーザーに提供していたらしいのですが、セキュリティ的に色々と問題があるようで、ほとんどのレンタルサーバー屋でホスト名で区分けする方式に変わっています。これ以後はSSL導入を前提に話しをすすめていきます。
CSRF・XSS対策
SSLはあくまで通信データを盗聴や改ざんから守るものであり、CSRFあるいはXSSへの対策は別に用意しなければなりません。これらスクリプティングによる対策は、「体系的に学ぶ安全なWebアプリケーションの作り方」が非常に参考になりました。XSS対策としては、HTMLのエスケープ処理に始まる基本対策と、入力文字に対する検証、スクリプトによるcookie漏洩を防ぐためのHttpOnly属性の付与、先ほどのSSL導入に合わせてcookieのsecure属性付与を行います。
一方のCSRF対策では、セッションIDなどのトークン埋め込みにより、正規ページからのリクエストであることの確認を行います。また、ここでAjaxを使用することはCSRFの保険的な対策になると考えられます。というのも、Ajax技術には制約として「同一生成元ポリシー」があり、Ajaxで送信できるリクエストは、送信元と同じドメインに限られるからです。つまりは、原理上自分のサイトを利用するユーザーが、自分のサイト以外からリクエストを送ってくる心配がないわけです。ここで「保険的」という言葉を使うのは、XHR Level2ではクロスドメインのリクエストを送ることができるからです。HTTP_X_REQUESTED_WITHヘッダでXMLHttpRequestかを判別する方法もありますが、攻撃者が偽装することが可能なので保険的な意味合いを出ない対策といえます。とはいえ、攻撃者の攻撃手段を狭めることに違いはないので、Ajaxの使用がセキュリティ的に問題ということにはなりまさん。
その他、SQLインジェクション対策に目を向けます。これは、入力値の検証とプレースホルダーを介したSQL文の実行によって、安全性を保証します。このプロジェクトで使用するSQLフレームワークはPHPのPEARライブラリのMDB2です。すでに主流はPDOに移ったと言われて久しいですが、PHPのバージョンをあまり気にしなくて済むこちらを自分は使用します。
パスワードの保存方法
大手企業サイトで個人情報漏洩に関するニュースをときより目にします。そして、このときパスワードを平文で保存していたということが、情報の漏洩とは別に問題になることがあります。たしかに企業やサイト運営側とすれば、パスワードは平文で保存できた方が、パスワードを忘れたユーザーから問い合わせを受けたときなど対応しやすく便利だったりします。しかし、万一パスワー ドが漏洩したときに、平文のまま保存していたらすぐさま悪用されてしまうわけですから、個人情報保護に関して厳しい今のご時世、平文保存はあり得ない選択肢ということになるでしょう。今回のプロジェクトでは、パスワードをユーザーからSSLを通じて受け取り、平文のパスワードに復号後、サーバー側でハッシュ値に変換してDBに保管することにしています。ハッシュ化あるいはメッセージダイジェストと呼ばれる手法は、メッセージを一方向的に暗号化するもので、変換後のハッシュ値から元のパスワードを推測するのが現実的に不可能とされています。それによって、万一保管されたパスワードのハッシュ値が漏洩しても、そこから元のパスワードが分からないようになっているので、直ちにユーザーの被害につながらないというわけです。さらに、ソルトとストレッチングという手法を組み合わせることで、パスワード解読をより困難にさせることができます。
ユーザーからパスワードを受けとるとき、チャレンジ・レスポンス方式というSSLとは別の暗号化方式をとることも可能ですが、こちらはユーザー側で暗号化処理をする必要があります。クライアント側のスクリプトはソースをのぞけばバレてしまうので、セキュリティ的に難があり今回はチャレンジ方式の採用を見送りました。
Ajaxページにおけるセッション(状態)管理については、長くなりましたので次回に回したいと思います。