悪意のある攻撃から身を守るには?
nonce:リプレイ攻撃に対する対処方法
第2回の最後で少し紹介しましたが、セキュリティーの大事な考え方の1つにnonce(ノンス)というものがあります。nonceとは、number used onceの略で、1回だけ使われる番号という意味です。ワンタイムトークンと呼ばれることもあります。
トークンというのは、文字の並びという意味で、セッションIDを使ったセッショントークンなどは、一般的な認証やフォームを使ったやりとりに幅広く利用されています。
ご存じのとおり、サーバーのリクエストは常にブラウザを使って人間が行うとは限りません。telnetを使ったり、プログラムを使うことによって、POSTやGETなどのHTTPリクエストができます。それは、悪意あるユーザーにとってみれば、強力な武器になり、例えば3秒ごとに認証を試みるといったことも、もちろん可能になります。
よって、セッションをベースとしたトークを使うことで、そのリクエストが同じセッションからのものであれば、リクエストを一定時間は受け付けない、という回避策をとることができます。
一方、nonceは1回だけ使われるワンタイムトークンですから、基本的には、同じリクエストが続けて来たら怪しいということになります。つまり、リプレイ攻撃のように正しいユーザーになりすましして訪問してきた悪意あるユーザーを見破るためにあるのです。
いずれにせよ、問題は悪意あるユーザーと正しいユーザーのリクエストが混在していることです。いかに正しいユーザーのリクエストにはきちんと応答し、そうでないユーザーのリクエストをはねつけるかということがポイントです。
OpenIDの場合には、第三者であるOP(OpenID発行サイト)に行って、また戻ってくるわけですから、1回だけ使えるトークンを発行して、同じトークンを持ったユーザーが次に来たらはねる、というnonceの考え方は、本当にそれが正しいユーザーかを確認するために有効です。
具体的な処理を説明しましょう。まず、OPから認証を受けてPR(ユーザーが利用したいサービス)に戻ってきた際に、DBに保存しているこれまで受け取ったnonceの中に同じものがないかどうかを確認します。この時、同じnonceが存在していれば怪しいユーザー、また存在していない場合でも、nonceの発行時刻が現在時刻と比べてかい離していれば怪しいユーザーとして拒否します。nonceにこうした問題がなければ、OPにおいて正しく認証されて戻ってきたユーザーと判断できますので、次のステップに進ませます。
なお、OpenID 2.0の仕様に、nonceだけでなくOPから受け取った値をどういう視点でチェックするのが正しいのかが書かれていますので、参考にしてみてください。
・openid.return_to(もともとPRが指定していたOPから戻ってきた場合のURL)の値がリクエストされたURLと一致するかどうか
・ディスカバリー時に得たOPの情報と一致するか
・同じ値のnonceを受け取っていないか
・署名が正しく十分なものであるかどうか
アソシエーションとセキュリティー
これまで説明した内容は、仕様上はユーザーエージェントを介したPRとOPでの間接的なコミュニケーションの部類に入ります。実は、この処理のほかにPRとOPの間に間接的ではない直接的なコミュニケーションもあります。
第1回、第2回の中で、OpenIDの処理を駐車場の入り口の係員と車の例えを使って説明しました。その中で、係員が車を誘導する前にAゲートに対して電話をかけるという手順がありました。つまり、これはユーザーをOPにリダイレクトする前に、PRとOPが直接通信することでOPとPRの間で信頼関係を築いているのです。
これが、「アソシエーション(association)」と呼ばれている処理で、日本語ではよく「関連付け」と訳されています。
このアソシエーションという概念は、Web上で見つけられる用語説明や技術的な説明だけを読んでもよくわかりにくいので、なるべく平たい言葉で説明してみましょう。
アソシエーションというのは「PRとOPだけが知っている秘密の呪文(じゅもん)を持つ」ということと言ってよいでしょう。メッセージのやりとりをする場合に、この秘密の呪文をかけて作ったコードを署名としてつけておくことによって、お互いに正しい相手をやりとりをしているということがわかるのです。
そして、この秘密の呪文をかけて作った署名のことを、MAC(Message Authentication Code)キーと言います。では、このMACキーはどのように作られるでしょうか。次ページで紹介しましょう。
ハッシュ関数とは
MACキーはハッシュ関数を使って作成します。ハッシュ関数というのは、手品で使われる黒い箱のようなもので、あるデータを入れるとそこから複雑になった別のデータが出てくる関数です。生成するビット長が異なるSHA-1やSHA-256があります。
ハッシュ関数の面白いところは、「出てきたデータ(ハッシュ値)を見ても元のデータが想像できないこと」と「同じデータを入れると必ず同じハッシュ値が得られること」です。一般的には、黒い箱に入れる前に、少し塩を振りかけてより元のデータをわかりにくくさせたりします。つまり、元データに+αのデータを足してインプットすることで、出てきたデータの強度が高まります。
アソシエーションの処理の中では、PRとOPがやりとりする秘密の呪文が元データとなり、ハッシュ関数にかけると、一意の署名(MACキー)ができるということです。
署名(MACキー)=秘密の呪文×ハッシュ関数
秘密の呪文の作り方
上記で説明した署名(MACキー)の目的はお互いに正しい相手かどうかを知るために利用するので、秘密の呪文自体をお互いに共有していなければいけません。しかもほかの人にはわからないように2人の間で秘密にしていなければいけません。
その方法が、Diffie-Hellman鍵交換(Diffie-Hellman Key Exchange)と呼ばれているプロトコルです。セキュリティー関連の本には必ず出てきますが、実際にその概念をきちんと理解することはなかなか難しいのではないでしょうか。
非常に簡単に言うと、自分の秘密の呪文と相手の秘密の呪文を使って2人しか知らない1つの秘密の呪文を作るということです。
例えば、AさんとBさんという人が2人の秘密の「呪文C」を作る場合、Aさんが自分の秘密の呪文Aから作った「呪文A'」をBさんに渡し、Bさんは自分の秘密の「呪文B」と合わせて、共通の呪文Cを作ります。Bさんから見た場合も同じです。
AがBに教えた「呪文A'」×Bが考えた「呪文B」⇒「呪文C」
BがAに教えた「呪文B'」×Aが考えた「呪文A」⇒「呪文C」
非常に単純化すると、以下のようになります。
「呪文A」=1
「呪文A'」=3
「呪文B」=2
「呪文B'」=4
「呪文C」=5
AがBに教えた「3」×Bが持っている「2」⇒「5」
BがAに教えた「4」×Aが持っている「1」⇒「5」
やりとりされるのは、つまり、盗聴される可能性のあるのは、AがBに教えた「3」とBがAに教えた「4」ですが、それぞれ盗んだとしても「5」を知ることはできません。
さらに、すでに説明したハッシュ関数で変換されているのでもともとのデータである「5」もわからないので、署名のMacキーを見たとしてもそこから元の値も知ることが難しくなるのです。
ここまで、アソシエーションを行う場合のセキュリティーに関して見てきましたが、実は、このようなアソシエーションを行わずに、OPから認証された後、PRに戻ってきてからあらためてOPに認証されているかを確認するstatelessモード(関連付けをしないモード)もあります。
mixiでもこのstatelessモードがサポートされていますが、PRが共有サーバーなどを使っていて、秘密鍵を保存しておけない、あるいは盗まれてしまう可能性がある場合にstatelessモードを使うことも可能です。
中間者攻撃とSSL
アソシエーションにおいて、これまで説明したきたような方法を使うことで、メッセージの改ざんなどのいわゆる中間者攻撃を防ぐことができそうですが、まだ十分ではありません。実はDNSサーバーやトランスポート層を操ることができれば、OPを装うことができるので、MACキーで署名したとしても防御が十分ではなくなってしまいます。
また、OPに対するリクエストの必要情報を得るディスカバリープロセスにおいて、例えばXRDSドキュメントを改ざんされる場合も考えられます。
こうした攻撃を防ぐためにもSSLを利用するべきでしょう。SSLについては、ここであらためて解説する必要はないかもしれませんが、暗号化された通信を提供するもので、なりすましや盗聴などを防ぎます。
通常はOPが信頼に足るかどうかを知らせるために、信頼できる認証局が発行したサーバー証明書を提示して、PRがそれが正しいことを確認した上でやりとりが行われますが、クライアント側の確認過程において、認証局自体の正当性を証明するためのルート証明書に行きつきます。
以前筆者がPHPのcurlを使ってmixiにSSL接続しようとしてうまくいかずに困ったことがありましたが、それはcurlが利用しているルート証明書が古いためのようでした。
curlのオプションでルート証明書の検証を行わないようにすることもできますし、そのように解説しているサイトもあります。しかし、mixiのサイト(http://developer.mixi.co.jp/openid/faq)にも説明されているように、この方法はなりすましの危険性があるため問題があると言えるでしょう。同じコードを流用する場合などもあると思いますので、避けた方がよいでしょう。
セキュリティーに対するスタンス
第2回、第3回とOpenIDに関連するセキュリティーについて簡単に説明してきましたが、攻撃手法などの危険性を中心に解説してきたので、なんとなくOpenIDに対する恐れのようなものが出てきてしまったかもしれません。
しかし、説明した攻撃手法の1つ1つは、それほど新しいものではないですし、すでに対応方法やコツなども確立しています。ですから、それぞれの立場での対処の仕方を理解し、それをきちんと行っていれば、実際それほど恐れるものではないと思います。
Webでいろいろなサイトを見ていると、「IDが1つだとそれを盗まれたらおしまいだ」という意見や、OpenIDのセキュリティー面での危険性を強調してあおるような意見などがあります。しかし、もともとWeb自体がそういった悪人と善人が混在するようなオープンな環境です。オープンであるからこそ危険性もある反面、いろいろな新しいサービスが花開いているわけですから、悪い面だけを見過ぎないで、適切に対処していく方がいいのではないかと思います。
さて、最終回となる次回は、実際にOpenIDを実装していく場合のステップやTipsについて具体的に解説したいと思います。