この頁では、HTTPに関するセキュリティ上の問題点や、それに対応するための仕組みなどについて解説します。
昨今、Webからの個人情報の流出が問題となっています。 HTTP経由で個人情報が流出してしまう可能性、およびその場合の被害について、RFC 2616のsection 15.1に記述されています。
HTTP クライアントはしばしば多くの量の個人情報 (例えばユーザの名前、場所、メールアドレス、パスワード、暗号キー、等々) を管理しているので、この情報を HTTP プロトコル経由で他のリソースへと知らないうちに漏洩していないように特に気をつけるべきである。 我々は、ユーザがそのような情報の公開についてを制御するための便利なインタフェースが提供される事と、設計者や実装者はこの部分を特に注意する事を特に強く推奨する。 歴史的に、この部分のエラーがしばしば深刻なセキュリティやプライバシー問題を引き起こし、その結果実装者の会社に対して不利な評判を高めている。
HTTPクライアントは、デフォルトの設定で、ユーザの入力量を削減するために、ユーザの名前、場所、メールアドレス、パスワード、暗号キー
などを保存しています。
そのため、以下のようなセキュリティリスクが常に存在します。
このようなリスクを低減するためには、HTTPアプリケーションの作者が、常にセキュリティ情報を収集し、注意すべきことはもちろんですが、そのHTTPアプリケーションを使用するユーザも同様にセキュリティに関心を持つ必要があります。
HTTPでは、様々な情報をやり取りされていますが、その中にはユーザの氏名や住所などの個人情報、あるいは製品情報や予算情報などの法人の機密情報も含まれているでしょう。 しかし、HTTPが利用しているTCP/IPプロトコルでは、いくつものサーバを経由してデータが転送するため、以下のセキュリティリスクを抱えています。
そこで、HTTPにSSL/TLSという技術を組み合わせて使われています。 SSL/TLSは、OSI参照モデルではトランスポート層(第4層)にあたるプロトコルで、以下のような機能を持っています
SSL/TLSは、HTTPの他にも、TCP/IPを使うプロトコルであるFTPやSMTPなどにも使用することができます。
ここまでSSL/TLSと書いてきましたが、これらはそれぞれ別のプロトコルです。
SSLは、Netscape社が元々Netscape NavigatorというWebブラウザのために設計したプロトコルでした。 しかし、設計途中にプロトコル上の不具合が発見されたため、実際には外部に公開されたり、実装されることはありませんでした。 実際に利用され始めたのはSSL 2.0(SSLv2)で、1994年11月に初めてその仕様が公開され、その後1995年3月にNetscape Navigator 1.1に実装されました。 SSLv2は、Netscape社が内部のセキュリティ担当者を中心に開発したもので、外部の意見や他ベンダからの情報提供もほとんどないままに開発されたので、他ベンダが実装をしようとすると様々な問題が生じました。 そこで、Microsoft社はSSLv2と同等の機能を持ち、かつ重大な欠陥を修正したPCTというプロトコルを1995年に開発し、Internet Explorer 2.0に実装しました。 SSLはプロトコル内にバージョン番号を持っていますが、PCTはこれを“2.0”としており、SSLv2との下位互換性を維持していました。 現状は、以下に示すSSL 3.0が普及したため、ほとんど使用されていません(※)。
(※)たとえば、Internet Explorer 8というWebブラウザの場合、SSLv2.0の有効/無効の設定欄はあるものの、初期状態は「無効」に設定されています。 また、Mozilla Firefox 8では、すでにSSLv2を有効にするという設定欄そのものがありません。
SSLv2の後継プロトコルとしては「SSL 2.1」というものが検討されていましたが、それは破棄されました。 1995年末、新たなプロトコルとして、SSL 3.0(SSLv3)が公開されました。 これに対抗する形で、Microsoft社も1996年にSSLv3の修正版であるSTLPを提案してInternet Explorerに実装しました。 現在「SSL」といえば、このSSLv3を表します。
SSLはデファクトスタンダードとしてすでに広く利用されていましたが、IETFは1996年、一私企業による暗号技術のSSLv3をベースに、TLSというオープンな暗号標準を作ることにし、1999年にThe TLS Protocol Version 1.0という文書が発行されています。 この中で定義された“TLS/1.0”は、SSLv3に若干の改良が加えられたものの、厳密にはSSLv3の上位互換ではありません。 しかし、実質的にSSLv3.1とも言えるプロトコルで、実際にプロトコル内の内部バージョン番号も“3.1”が使用されています。
その後、2006年にはThe Transport Layer Security (TLS) Protocol Version 1.1が、また2008年には The Transport Layer Security (TLS) Protocol Version 1.2がそれぞれ発行され、新しい暗号化方式に対応しています。
SSL/TLSがデファクトスタンダードとなるまでには、これ以外にも色々な形式の暗号化方法が提案されてきました。 その中でも有名なものに、Enterprise Integration Technologies社によって提唱されたSHTTP(RFC 2660)があります。 SSL/TLSは「通信経路」にセキュリティをかけるのに対し、SHTTPでは「送信される個々のデータ」にセキュリティをかけるもので、目的が違うため、理論上は同時に使用できます。 提案された1995〜1996年頃には、SSL/TLSとSHTTPのどちらが主要なセキュリティプロトコルになるかはわからない時代もあったようですが、現時点では明らかにSSL/TLSが主流となっており、SHTTPを実装するHTTPアプリケーションはありません。
HTTPでSSL/TLSを利用する場合には、二つの方法があります。
一つは専用のポート番号(一般には443)を使用して、初めからSSL/TLS上でアプリケーションを実行する場合でというものです。
これは元々Netscape社によって提案された方法で、HTTPSと呼ばれ、HTTPに関するRFCとしてはHTTP Over TLSが公開されています。
もう一つは、「通常のポート(一般には80)でHTTPアプリケーションを起動後にSSL/TLSを呼び出す」という方法で、HTTP/1.1ではUpgrading to TLS Within HTTP/1.1にて、Upgradeヘッダを使って行う方法が示されています。
(※) ただし、現在では、専用ポートを利用する方法はIETFは非推奨としています。
通信連鎖上にプロクシなどが介在する場合には、CONNECTメソッドを使って、リクエストをトンネリングさせます。
本節では、SSL/TLSの特徴について記述します。 上述のとおり、SSL 3.0とTLSは、厳密には異なるものの基本的には同じプロトコルなので、以下の特徴も共通したものです。
SSL/TLSでは、互いの認証に公開鍵暗号技術を用いています。 公開鍵暗号方式は、発信元でのデータの暗号化と、受信先でのデータの復号化に、異なる鍵を使用するのが大きな特徴です。 暗号化に使用される鍵を公開鍵、複合化にに使用される鍵を秘密鍵と言います。 公開鍵は、文字通り「広く公開しておく鍵」であり、秘密鍵は「生成したユーザが秘密にしておく鍵」です。 この二つの鍵の生成方式には、いくつかの種類がありますが、特に有名なアルゴリズムに、Rivest, Shamir, Adlemanの三氏によって、1978年に開発されたRSAアルゴリズムがあります。 RSA暗号を解読するには巨大な整数の素因数分解が必要があり、現在この暗号を解読することは現実的には不可能であるとされています。
SSL/TLSによる通信では、まずクライアントがClientHelloメッセージを送信し、サーバはServerHelloメッセージで応答します。
これを「SSL Hello(交換)」と呼び、この中で、以下の情報を決定します。
ClientHello.random, ServerHello.random)これに成功すると、サーバは証明書認証局(CA {certificate authorities})によって証明された自らの証明書をクライアントに送信します。 証明書内にはサーバの公開鍵があり、この鍵をランダム値で暗号化して共有鍵が生成されます。 以降のデータのやり取りは全て共有鍵にて暗号化されます。
スプーフィング{spoofing} は、「なりすまし」等と訳されます。 なりすましの危険性について、section 15.3 をご覧下さい。
HTTP を使用しているクライアントは Domain Name Service に非常に頼っており、従って一般的に IP アドレスと DNS 名の故意なる間違った組み合わせをベースとしたセキュリティアタックが行われる傾向にある。 クライアントは、IP アドレス/DNS 名の組み合わせの正当性の連続についての仮定にて注意深くある必要がある。
特に、HTTP クライアントは前回のホスト名 lookup の結果のキャッシュよりも、IP アドレス/DNS 名組み合わせの確認についてはそのネームリゾルバを頼るべきである。 多くのプラットフォームは適切な時期に既にローカルにホスト名 lookup をキャッシュできるので、そうするように設定すべきである。 しかし、これらの lookup はネームサーバによって報告された TTL (Time To Live) 情報がキャッシュされた情報がおそらく有効であるであろうとした時にのみキャッシュされるのが適切である。
もし HTTP クライアントがパフォーマンスを改善させるためにホスト名 lookup の結果をキャッシュするなら、DNS によって報告される TTL 情報を監視しなければならない。
もし HTTP クライアントがこの規定を守らないと、それらは直前にアクセスしたサーバの IP アドレスが変更された時にだまされる。 ネットワークの数値再割り当てがますます一般的になってくる事が予想されるため [24]、この形式のアタックの可能性は高くなる。 従って、この要求を監視する事によってこの潜在的なセキュリティの弱さを減らす。
また、この要求は同じ DNS 名を使用している複製されたサーバに対してクライアントのロードバランシング{load-balancing} 動作を改善し、この作戦を使うサイトをアクセスした場合にユーザが直面する失敗の可能性を減らす。
DNS とは、あるドメイン名とそれに対応する IP アドレスの変換を行う仕組みです。 しかし、この変換が正しく行われないと、意図しないホストへアクセスしてしまう事になります。 上の文書では、ホスト名をキャッシュしたデータが古くなっていた場合に意図しないアクセスをしてしまうという事が述べられていますが、この変換テーブルが悪意を持って変更された場合には、より深刻な被害を引き起こす可能性があります。
特に、ドメイン所有者と無関係な者が本来の所有者になりすまして登録情報の変更申請を行う等して、ドメインを管理するサーバのアドレスが本来のものとは別のものに書き換えられる事をドメインジャック (あるいはドメインハイジャック) と呼ぶ事があります。 ドメインジャックをされると、そのドメインは手続き上なりすましを行った者が所有している事になるので、そのドメインでアクセスされる Web も意図しないものに差し替えられてしまうだろうし、電子メールも本来の所有者が管理する所へは届きません。 従って、この問題は全てのドメイン管理者にとって関係のある問題です。 特にネームサーバを外部に委託しているような場合、そのネームサーバについてもドメインジャックを考慮する必要があるので、更なる注意が必要になります。
「サービス拒否(DoS)攻撃」とは、相手のコンピュータやルータなどに不正なデータを大量に送信することにより、相手のネットワークを麻痺させ、サービスの提供を不能にさせるという攻撃です。 また、数千台、数万台単位のコンピュータを利用して、特定のサーバに向けて一斉にパケットを送出することでサービス拒否攻撃を行なうことを「分散型サービス拒否(DDoS)攻撃」といいます。
プロクシを使ったサービス拒否攻撃については、RFC 2616のsection 15.7.1に記述があります。
この攻撃は存在する。この攻撃を防御する事は難しい。調査を続けよ。用心せよ。
基本的に、この攻撃は通常のHTTPアクセスと同じ手順を踏むため、この攻撃そのものを排除することはできません。 ただし、DoS攻撃に関して言えば、他人から不正なプログラム(トロイの木馬)を仕掛けられることによって、意図しないまま攻撃に参加されられてしまう(※)場合があります。 コンピュータの管理者は、定期的にウィルススキャンなどを行い、自分が攻撃者とならない様にすべきです。
(※) このようなコンピュータは、一般に踏み台と呼ばれます。
Webで動的なリソースを提供するために、サーバ上でスクリプトを動作させ、ユーザに返すリソースを生成するという仕組みがあります。 このような仕組みとして、たとえばCGI、PHP、Javaサーブレットなどがあり、それを実行するためのスクリプトをサーバサイドスクリプトと呼んでいます。
サーバサイドスクリプトは、しばしばセキュリティホールを産み出す原因となりますので、サーバサイドスクリプトを開発する人は、サーバそのものの開発者と同じくらいの注意を払いながら作成をしなければなりません。 セキュリティの低いスクリプトによって、たとえばサーバ内を勝手に探査されたり、サーバログを読まれてしまったり、パスワードファイルをメール送信されたりなど、悪用され、多大な被害を被る可能性があります。
以下では、CGIスクリプトを例に挙げ、セキュリティリスクについて記述しますが、これらはどれも言語やシステム形態に依存しない、普遍的なリスクポイントです すべてのサーバサイドスクリプト作成者は、以下のような点を特にご注意ください。
外部に公開されるスクリプトを作成する場合、ユーザから入力される情報は信用してはいけません。 RFC 3875のsection 8.2では、CGIスクリプト作成時に推奨されることが記述されています。
スクリプトが PATH_INFO データを処理するつもりでない場合、PATH_INFO が NULL でなければ、404 Not Found をもってそのリクエストを拒否すべきである。
フォームの出力が処理される場合は、CONTENT_TYPE が "application/x-www-form-urlencoded" [18] か "multipart/form-data" [16] である事をチェックせよ。 CONTENT_TYPE が空白の場合、そのプロトコルによってサポートされていれば、スクリプトは 415 'Unsupported Media Type' エラーをもってそのリクエストを拒否できる。
スクリプトは、PATH_INFO, PATH_TRANSLATED, SCRIPT_NAME を解析する時、空のパス要素 ("//") や特別なパス要素 ("." や "..") に注意すべきである。 OS システム呼出しにて使用する前にパスからこれらを取り除くか、404 'Not Found' をもってそのリクエストを拒否すべきである。
上記の例では、CGIについて記述されていますが、CGIに限らず、すべてのサーバサイドスクリプトでは、アクセスされるディレクトリやファイルを限定しなければなりません。 そうしなければ、たとえば/bin/passwdなどの重要な機密ファイルにアクセスされ、その結果サーバに自由にアクセスされたり、サーバ上のあらゆるファイルを改変・削除されてしまうという甚大な被害を引き起こす可能性があります。
サーバサイドスクリプトにて、ユーザからの入力に従ってファイルにアクセスする場合は、必ずその整合性をチェックしなければなりません。 たとえば、open FILE, "data/$CGI{name}.dat"のようなことをしてはいけません。
現在、Webサーバを動作させているOSのうち、かなり多くは、Linux系やBSD系などの“UNIX互換のOS”です。 RFC 3875のsection 7.2では、そのようなUNIX互換のOSにおけるCGIのシステム仕様について記述しています。
UNIX 互換の OS については、以下が定義される:
- 外部変数
- 外部変数は、同一に名付けられた環境変数にてスクリプトに渡される。 これらへは、C ライブラリルーチン getenv() やそれに関する変数によってアクセスされる。
一般に、環境変数とは「OSのシェルなどに設定されている、システムの属性を記録している変数」のことを指します。 UNIX互換OSでは、CGIなどの、サーバサイドスクリプトで使用する外部変数を、環境変数として設定します。 たまに、「HTTP_で始まるCGI外部変数」のことを「環境変数」と呼んでいる人がいますが、これは正確な使用方法ではありません。
また、「HTTP_で始まるCGI外部変数」をユーザに見せたいがために、サーバOSの環境変数のすべてを開示してしまっているサーバサイドスクリプトがありますが、これはサーバのセキュリティを下げる非常に危険な行為です。 例えば、DOCUMENT_ROOTという環境変数は、サーバ上の配置構成を知らしめる一端を担いますし、PATHという環境変数によって、実行可能なプログラムのヒントを与える可能性があります。 したがって、「HTTP_で始まるCGI外部変数」を見せたい場合は、それ以外の環境変数は見えないようにしなければなりません。
(※) CGIで使用する「環境変数」については、CGIで使用される外部変数を参照ください。 また、それらを表示するためのCGIスクリプトのサンプルは、env_with_cgi_pm.cgiをご覧ください。
基本的に、SSLあるいはTLSを利用していない通信は全て傍受されているとみなしてもよいのですが、それは極端としても、第三者がパスワードや個人情報等の入力情報を簡単に入手できないように、スクリプトの作者は注意しなければなりません。 RFC 3875のsection 9.3をご覧ください。
リクエスト内の機密データは POST リクエストの一部として message-body 内に置かれるべきであり、URI やメッセージヘッダ内に置かれるべきではない。 システムによっては、スクリプトに外部変数を渡す環境が他のスクリプトやユーザから見えるかもしれない。 加えて、多くの既存のサーバやプロクシ、クライアントは、第三者から見られる所に URI を永続的に記録しているであろう。
クライアントからパスワードなどの機密情報を受け取る際に、もしGETメソッドを利用してしまうと、仕様上そのメッセージボディはURI中に露出されてしまいます。 そのような場合、サーバや中継するプロクシのログに、そのURIがそのまま記録されてしまいます。 サーバやプロクシの管理者は、アクセスログを個人情報に準じるものとみなし、第三者が閲覧可能な場所に放置しておくべきはありませんが、仮にそれが第三者に見られてしまった場合、そこから個人情報が漏洩する可能性があります。 同様に、HTTPヘッダやHTTP Cookiesなども、ログ上に残されている可能でがあるので、その中に機密情報を含めるべきではありません(Cookiesは、基本的にHTTPヘッダの一種です)。 したがって、機密的な情報は、POSTメソッドなどを利用し、簡単にログに残らないようにしなければなりません。
(※) ただし、これは「一般に、POST の message-body はログにとらないから、そうしておけば危険性が減る」と言っているに過ぎません。 サーバ管理者が、どのデータをログに残すかはサーバの設定次第ですし、また上述の通り、HTTPではあらゆるデータが平文で流れるのですから、よりリスクを下げたければ、やはりSSLあるいはTLSなどを利用した暗号化通信を行う必要があります。