さくらインターネットの公式FAQに次の記述があるのに気づいた。
Cookieは、パスなどを指定することができるため、初期ドメイン以外では共有SSLを利用している場合にCookieのパスを正しく指定しないと、同じサーバの他ユーザに盗まれる可能性があります。
(略)
上記については、「同サーバを利用しているユーザだけがCookieをのぞき見ることができる」というごく限定的な影響を示していています。また、Cookieの取扱いについて、問い合わせフォームやショッピングカート等、ビジネス向けのウェブコンテンツを設置されていなければ特に大きな問題とはなりませんが、個人情報を取り扱われる管理者であれば一読いただきたい内容であると判断し、掲載しています。
「ごく限定的な影響」というのはどういうつもりなのか疑問*1だが、この注意書きをちゃんと載せているあたりは、さすがはさくらインターネットと言える*2。しかし、「安全な設定」として紹介されている以下の記述は誤りであり、全く安全でない。
パス /example.com/
https://secure101.sakura.ne.jp/example.com/ のCookieは https://secure101.sakura.ne.jp/hoge.net/ のCGIでは取得できない。
Set-Cookie: における「path=...」指定は、じつはセキュリティ上何の効果もない*3ことが昔から知られている。
私が知ったのは2001年11月のことで、セキュリティホールmemoメーリングリストの[memo:2016]で以下のように書いている。
From: TAKAGI Hiromitsu <takagi.hiromitsu@aist.go.jp> Subject: [memo:2016] クロスサイトスクリプティングとcookieの有効path Date: 2001年11月26日 17:03:58 JST (略) クロスサイトスクリプティング対策として | cookieの有効pathを設定するとよい | - サイトの一部でしか使用しないcookieはpathの有効範囲を絞って発行す | ることで、クロスサイトスクリプティングの影響範囲を狭められる と書きましたが、これは有効な対策にならないことがわかりましたので、この 場をお借りして、訂正します。 以下、なぜ対策にならないかの説明です。 必要なCookie、例えば「sessionid」を Set-Cookie: sessionid=1234567890; path=/serviceA/cgi/; で発行したとします。 このcookieは、 http://example.com/serviceA/cgi/foo.cgi のページに埋め込まれたスクリプトからはアクセスできますが、 http://example.com/serviceB/cgi/foo.cgi や、 http://example.com/foo.html のページに埋め込まれたスクリプトからはアクセスできなくなります。これが Set-Cookie: のpath指定のはたらきです。 ここで、 http://example.com/serviceB/cgi/foo.cgi にクロスサイトスクリプティング脆弱性があったとしましょう。例えば、 http://example.com/serviceB/cgi/foo.cgi?<SCRIPT>...</SCRIPT> にアクセスするとそのページ上でこのスクリプトが動いてしまうとします。 そのスクリプトから目的のcookieにアクセスしようとしても、上に説明したよ うに、「path=/serviceA/cgi/」の指定されたcookieにはアクセスできません。 ところが、ここで、<FRAMESET>を使ってダミーのFRAME (pathがcookieの有効 パスと同一) を生成されると、そのFRAMEのdocument.cookieにアクセスされて しまい、目的のcookieを盗られてしまいます。 http://example.com/serviceB/cgi/foo.cgi?<FRAMESET><FRAME NAME="subframe" SRC="/serviceA/cgi/bar"><FRAME SRC=foo.cgi?<SCRIPT>alert(parent.subframe. document.cookie)</SCRIPT>"></FRAMESET> こんな感じです。parent.subframe.document. とフレームを辿ることによって、 異なるpathに限定して発行されたcookieにアクセスできてしまいます。 この手法は、先日の、Marc Slemko氏の「Microsoft Passport to Trouble」 http://alive.znep.com/~marcs/passport/ で知りました。
このときは、クロスサイトスクリプティング脆弱性への保険的対策としてのpath指定の無効性について述べているが、クロスサイトスクリプティング脆弱性の有無によらず、ホスト名が同一の共用サーバ上で、パス名で区切られた領域に異なる利用者によるページが混在する場合においても同様である。
通常のレンタルサーバでは、今日ではほとんどの場合、ホスト名で利用者を分けている*4ため、この問題の影響を受けないが、SSLの共用サーバとなると、サーバ証明書を1つで済まそうとするためか、同じホスト名のサーバ上にパス名で分けて提供するところが後を絶たない。
同じホスト名のサーバ上にパス名で分けている場合でも、たとえば、2009年6月27日の日記で示した共用SSLサーバでは、事前に用意された入力フォームしか設置することができないものであるため、この問題の影響を受けない。また、2009年8月26日の日記で示したエコポイントの申請画面が置かれたセールスフォースの件では、eco-points.secure.force.com というホスト名で分けられているため、この問題の影響を受けない。(ただし、これらには別の問題があり、その点については、それぞれの日記に書いている。)
さくらインターネットの場合は、自由にCGIプログラムを設置できるというくらいであるから、JavaScriptの記述は自由なのだろう。そうすると、さくらインターネットが挙げている以下のケース
は、実際には、たとえ example.comがpath指定無し、あるいは「path=/example.com/」の指定でcookieを発行していたとしても、攻撃者は、https://secure101.sakura.ne.jp/hoge.net/ 上に以下のようなスクリプトを設置することで、example.comのcookie等を取得することができてしまう。https://secure101.sakura.ne.jp/example.com/ のCookieは https://secure101.sakura.ne.jp/hoge.net/ のCGIでは取得できない。
<iframe src="/example.com/" name="foo"></iframe> <ul> <li><a href="javascript:alert(foo.document.cookie)">Get cookie</a> <li><a href="javascript:alert(foo.document.body.innerText)">Get text</a> <li><a href="javascript:alert(foo.document.body.innerHTML)">Get html</a> </ul>
この例にあるように、取得できるのはcookieだけではない。cookie(やBASIC認証など)を用いてログイン状態となっているページのコンテンツ(秘密であるはずの)も取得できてしまうし、コンテンツの内の入力欄やボタンを操作して、あらゆる不正な操作が可能になってしまう。
これはWebセキュリティの基本で、same-origin policyの「origin」はホスト名で区分される*5ものであって、パス名では区分されないことを忘れてはいけない。
では、同じホスト名の共用サーバ上(他の利用者が自由にCGIやJavaScriptの記述ができるところ)において、どういうことならやっても安全か(共用する他の利用者に攻撃される余地がないか)というと、それはなかなか簡単に結論がでない。仮に条件を示せたとしても、利用者に理解可能なものにはならないと思われるので、そもそもこのような共用SSLサーバのサービスはやめるべきだろう。少なくとも、ホスト名で分けて*6、サーバ証明書を一枚で済ませたいならワイルドカード型の証明書を使えばよい。
さくらインターネットの場合、ホスト名で利用者を分けた共用SSLが、既に追加料金なしに利用できるようになっていた。FAQのページで「初期ドメイン」と呼ばれているもの。それならば、パス名で分けた共用SSLは廃止して、「初期ドメイン」方式へ移行するよう利用者に呼びかけるべきだろう。
*1 このような不正直な表現をする事業者を信頼できるだろうか。
*2 何ら注意書きしていない共用SSLサーバ提供事業者ばかりである中で。
*3 同じ名前のcookieを共存させることができるくらいしか、pathを分けることの意味はない。
*4 パス名で分けているところでは、ページ内にJavaScrpt等を記述させないための対策(広義のクロスサイトスクリプティング対策)がとられている。たとえば、はてなダイアリーがそれに該当する。
*5 正確には、hostとschemeとportによって区分される。
*6 その場合でも、2009年6月27日の日記や2009年8月26日の日記で示した問題点は残る。