第2回「間違いだらけの『かんたんログイン』実装法」ですが、多くの方に読んでいただきありがとうございました。
今回は、前回に引き続き架空のSNSサイト「グダグダSNS」のケータイ対応を題材として、ケータイWebのセッション管理の問題点について説明します。携帯電話向けWebアプリケーション(ケータイWeb)のセッション管理は、かんたんログインよりも対策が難しく、厄介な問題なのです。
A社が「グダグダSNS」サイトを携帯電話に対応させるに当たり、かんたんログインに加えて、セッション管理の方法を決定する必要がありました。
A社の開発者は「そういえば、ケータイではCookie使えないんだよね」ということに思い当たり、ケータイWeb向けのセッション管理方法をWebで検索して調べました。すると、次の2種類の方法が、ケータイ向けサイトにおけるセッション管理の主流のようでした。
このうち、契約者固有IDをキーとする方法は少し難しそうだという理由から、A社はセッションIDをURLに埋め込む方法を採用することにしました。PHPを用いてURLにセッションIDを埋め込む方法を調べてみたところ、php.iniに以下の2行の設定を書くだけで実現できることが分かりました。
session.use_cookies = 0 session.use_trans_sid = 1
これは、セッションIDの保存にクッキーを使わない(session.use_cookies = 0)設定と、URLにセッションIDを自動的に埋め込む(session.use_trans_sid = 1)設定を組み合わせたものです。
A社のエンジニアは、念のため簡単なスクリプトを作成して動作を検証してみました。以下はセッションを開始した後、next.phpおよびhttp://www.atmarkit.co.jp/へのリンクを表示するだけの簡単なスクリプトです。
<?php session_start(); ?> <body> <a href="next.php">next</a><br> <a href="//www.atmarkit.co.jp/">@IT</a> </body>
これを実行すると、以下のHTMLが生成されます(PHPSESSIDは異なる値になります)。
<body> <a href="next.php?PHPSESSID=g631b9am8036q0c6nf5h5v4b53">next</a><br> <a href="//www.atmarkit.co.jp/">@IT</a> </body>
内部のリンクについては自動的にセッションIDが付き、外部ドメインへのリンクにはセッションIDは付きません。これを見てA社のエンジニアは「URLにセッションIDを埋め込んでもセッションIDが漏えいすることはない」と判断し、実装しました(注意:この判断は間違っています)。
「グダグダSNS」がケータイ対応してからしばらく経ったある日、外部のセキュリティ専門家から「セッションIDが外部に漏えいするので成りすましの危険性がある」と指摘されてしまいました。
しかし、前述のようにA社のエンジニアは、外部サイトへのリンクにはセッションIDが付かないことを確認していたはずです。不思議ですが、指摘を読むと以下の理由であることが分かりました。
「グダグダSNS」には日記の機能があり、日記中にURLが記載されている場合は、自動的にa要素に変換されます。これはSNSでは一般的な機能です。しかし、この機能を悪用した攻撃が可能です。以下、攻撃のシナリオを説明します。
まず攻撃者はワナのサイトを用意して、「グダグダSNS」の日記に次のように書き込みます。
私のホームページを作りました。 見に来てね http://trap.example.com/
この日記をグダグダSNSに投稿すると、以下のHTMLに変換されます。
<p>私のホームページを作りました<br> 見に来てね</p> <a href="http://trap.example.com/">http://trap.example.com/</a>
ご覧のように、a要素のURLにはセッションIDは付いていません。
しかし、この日記を利用者が閲覧すると、次のようなことが起こります。例えば、日記のURLがhttp://example.jp/435?PHPSESSID=8A4E13Bだとすると、このURLに付いているセッションIDが、遷移先のサーバにRefererヘッダとして送信されるのです(図1)。Refererヘッダとは、遷移元のURLを送信するもので、通常はサイトのアクセス解析に利用されます。
このように、たとえ外部へのリンクURLに直接セッションIDが含まれていなくても、リンク元ページのURLにセッションIDが付いていると、Refererヘッダにより外部のサイトにセッションIDが漏えいすることが分かりました。
この問題に対処するため、A社は、セキュリティ専門家からの報告書に掲載されていた「クッションページ」を使う方法を採用することにしました。
クッションページとは、外部サイトにリンクする際に直接リンクするのではなく、「中間のページ」を挟む方法のことです。図2にクッションページを用いた画面遷移例を示します。真ん中のページがクッションページであり、このページのURLにはセッションIDが付かないようにします。
この遷移の場合、外部サイトに送信されるRefererはクッションページのURLを指すことになります。クッションページにはセッションIDが付かないので、外部サイトにはRefererヘッダを介しても情報が漏れなくなります。
php.iniにsession.use_trans_sid = 1を指定してセッションIDを自動的に付与している場合には、クッションページのURLにもセッションIDが付与されます。これを防ぐ方法はいくつかありますが、簡単に実現するには、クッションページを絶対URLで指定する方法があります。絶対URLで指定されたURLに対しては、PHPはセッションIDを付けないからです(下記のhref属性の指定)。
<a href="http://example.jp/redirect?url=http://trap.example.com/">~</a>
クッションページの導入は、RefererからのセッションID漏えい防止以外にも効果があります。それは、利用者に「外部のサイトに遷移する」という事実を明確に示せることです。
携帯電話のブラウザではアドレスバーが表示されません。このため、利用者が外部サイトに遷移したかどうかを確認しにくい場合があります。
この特性を悪用すると、実際には外部サイトに遷移しているのに、画面デザインを元のサイトに似せることでフィッシングに悪用できる可能性があります。フィッシングとは、正規サイトそっくりの画面を別サイトで用意することにより、利用者にユーザーIDやパスワード、その他の個人情報などを入力させる詐欺手口のことです。
これに対しクッションページを用いると、「これから先は外部のサイトである」ことを利用者に明確に伝え、フィッシング被害に遭う可能性を減らすことができます。
A社は外部リンクをクッションページ経由にすることにより、Referer経由でのセッションID漏えいに対策しました。これで一件落着、グダクダSNSは、しばらく特に問題なく運用されていました……が、今度は利用者から、「プロフィール画面に別人の情報が表示される」という問い合わせが時々くるようになりました。
別人の情報が表示される手順を詳しく問い合わせたところ、いずれも検索サイト経由でSNSを閲覧していることが分かりました。セキュリティ専門家にも入ってもらって解析したところ、以下のシナリオで情報が漏えいしていることが分かりました。
別人の情報が表示された利用者へのヒアリングの結果、この利用者たちは検索サイト経由で「グダグダSNS」にアクセスしていることが分かりました。何らかの原因により「グダグダSNS」のセッションID付きURLが検索サイトに登録され、それにアクセスした複数の利用者が同じセッションIDの状態で「グダグダSNS」にアクセスしていたのです。
セッションIDは利用者ごとにユニークであることを前提としています。当然、同一のセッションIDを複数のユーザーが利用すると不具合が発生します。具体的なシナリオは以下のとおりです。
図3のように、「グダグダSNS」のURLがセッションID付きで検索サイトに登録されています。利用者XさんがこのリンクをたどってSNSに個人情報を登録する【1】と、セッションに個人情報が保存されます【2】。
次に、利用者Yさんが検索サイトのリンクをたどってSNSを閲覧し、そこから個人情報表示の画面を閲覧します【3】。XさんとYさんは同じセッションIDを共有しているので、セッション上の個人情報が表示された場合、YさんはXさんの個人情報を閲覧することになります【4】。
この種の問題は「セッションIDの固定化」(Session Fixation)と呼ばれる脆弱性です。現実に、この脆弱性が原因となって発生した個人情報漏えい事故が発生しています。
セッションIDの固定化は、CookieにセッションIDを保存する場合にもブラウザの脆弱性により発生する場合があります。しかし、セッションIDをURLに埋め込んだ場合は、ブラウザに脆弱性がなくても、簡単に発生してしまうことがより問題です。
【関連記事】
安全なセッション管理を実現するために
http://www.atmarkit.co.jp/fsecurity/rensai/struts04/struts01.html
セッションIDの固定化に対処する方法としては、ログイン時にセッションIDを変更することが有効です。PHPの場合は、以下のようにしてセッションIDを変更することができます。
session_start(); session_regenerate_id(TRUE);
しかし、この方法では、ログインしていない状態でのセッションIDの固定化問題には対処できません。ログイン前のセッションIDの固定化に対処することは難しいのが現状です。以下にいくつかの方法を紹介します。
ログイン前にはセッション管理機構を使わずに、hiddenパラメータによりデータを引き回しする方法があります。ログイン前のセッションIDの固定化に対処する方法としては唯一の確実な方法です。
セッションIDをCookieに保持すると、ブラウザなどに脆弱性がない限り、セッションIDの固定化は発生しません。このため、Cookieが利用できる端末ではCookieにセッションIDを保持することにより、セッションIDの固定化を起こりにくくすることができます。
Cookieが使える端末ではCookieにセッションIDを保持し、Cookieが使えない場合のみセッションIDをURLに保持するためには、PHPの場合、以下の設定が有効です。
session.use_cookies = 1 session.use_only_cookies = 0 session.use_trans_sid = 1
将来、Cookie未対応の端末が少なくなったタイミングで、セッションIDをCookieのみに保持するようにするとよいでしょう。その場合は、php.iniの設定を以下のように変更するだけで対応できます。
session.use_cookies = 1 session.use_only_cookies = 1 session.use_trans_sid = 0
「検索サイトを起点としたセッションIDの固定化」で紹介したように、検索サイトを起点としてセッションIDの固定化が発生する可能性があります。
検索サイトでは、クローラと呼ばれるプログラムがサイトを巡回してサイトの情報を収集しています。そこで、クローラからのアクセスに対しては、セッションIDを出さないことにより、セッションID付きのURLが検索サイトに登録される事態をある程度防ぐことができます。
しかし、他のサイトからセッションID付きでリンクされている場合には、セッションID付きのURLが検索エンジンに登録される場合があるので、確実な方法とはいえません。
URLにセッションIDを埋め込むことによって起こる、RefererからのセッションID漏えいとセッションIDの固定化という2つの問題を紹介しました。
セッション管理はWebアプリケーションの基本ですが、一部の携帯電話向けブラウザがCookieをサポートしていないために、安全なWebアプリケーションの開発が難しくなってしまうのは残念なことです。
2年ほど前までは、携帯電話向けWebサイトのほとんどがCookieを使わずにサイト開発していたようです。しかし最近では、携帯電話向けWebアプリケーションでもCookieを利用するサイトが増えてきました。また、前回紹介したように、NTTドコモの携帯電話が2009年夏モデル以降でCookieに対応するなど、対応端末も増えてきています。まだ完全にCookieを必須とすることは難しいかもしれませんが、今のうちにCookie完全移行に向けた準備をしておくとよいでしょう。
京セラコミュニケーションシステム株式会社
プラットフォーム事業本部 技術顧問
HASHコンサルティング株式会社
代表取締役
徳丸 浩(とくまる ひろし)
1985年京セラ株式会社入社、1995年京セラコミュニケーションシステム株式会社(KCCS)転籍、2008年HASHコンサルティング株式会社設立。現在、京セラコミュニケーションシステム株式会社 プラットフォーム事業本部 技術顧問を兼務。システム開発の経験を経て、2004年からWebセキュリティのサービスを開始。特にケータイWebサイトのセキュリティについては早くから着眼し、多くの講演、寄稿を手がける。
HASHコンサルティングを立ち上げてからは、企業のWebサイト開発のコンサルティングなどにも幅広く携わる。
最近、特に注目されているWAFについては、多くの実証実験を手がけ、自身のブログでもWAFについての見解を述べている。
▼徳丸浩の日記
http://www.tokumaru.org/d/
▼KCCSセキュリティサイト
http://www.kccs.co.jp/security/index.html