修論書きがようやく終わり、久しぶりの投稿です。
その手始めに、Webセキュリティの基本のキである同一オリジンポリシーやCORSについてまとめたものを書き残したいと思います。
同一オリジンポリシー
オリジン
はじめにそもそもオリジンとはなんなのかを説明します。
オリジンとはウェブコンテンツにアクセスするために使うURLの中のプロトコル、ドメイン、ポート番号という3つの値の組のことです。
もっと直感的にはウェブコンテンツの配給元の詳細で、どのサーバーから(ドメイン)どの出口を通って(ポート番号)どのよう配給されたのか(プロトコル)という情報です。
例えば
http://example.com/app1/index.html
http://example.com/app2/index.html
というhtmlファイルの位置を示す2つのURLがあったとします。
このとき、1のhtmlファイルについて言えば、
- プロトコル:http
- ドメイン:example.com
- ポート番号:80(WebサーバはHTTPコンテンツを配信するのに80番を既定で使う)
となり、この組み合わせがそのままオリジンと呼ばれます。
一方で、2のhtmlファイルのオリジンはどうかというと、実は1のhtmlファイルのオリジンと同じになります。
このように2つのリソース(ファイルなど)が同じオリジンから提供されていることを同一オリジンであるといいます。
同一オリジンポリシー
上の説明を読めばだいたい分かるかもしれませんが、同一オリジンポリシーというのはあるリソースについて、そのリソースからアクセスできるのは同一オリジンのリソースに限るという約束事のことです。
同一オリジンポリシーを守ったリソース間アクセスをざっくりと図解すると次のようになります。
この場合、ファイルAもファイルBも共にhttp://example.com:80
という同一のオリジンから提供されたファイルなのでファイルAからファイルBへは同一オリジンポリシーに反することなくアクセスできます。
逆に、同一オリジンポリシーに反するリソース間アクセスは次のようになります。
この場合、ファイルAのオリジンはhttp://example.com:80
である一方で、ファイルBのオリジンはhttp://www.example.com:80
でありこれらはドメインが異なっているため異なるオリジンのリソースです。
なので、ファイルAからファイルBへのアクセスは同一オリジンポリシーに基づいて制限されます。
同一オリジンポリシーが必要な理由
どうして同一オリジンポリシーなんて言うめんどくさいルールがあるのか?
それはひとえに、これを設けることで防げる攻撃があるためです。
その例を下に示します。
これは罠サイトを使ったサイバー攻撃の一つで、次のような仕掛けになっています。
まず、利用者はexample.comにアクセスしようとします。
しかし、誘導により罠サイトのtrap.example.comを閲覧してしまいます。
trap.example.comから送られたhtmlにはexample.comのページが埋め込まれており、パッと見では罠にかかったと気づきません。
そして、利用者はパスワードなどを打ち込んで送信します。
このような仕掛けはhtmlのiframe要素を使えば作ることができます。
この時、仮に同一オリジンポリシーが無いとすればtrap.example.comから送られたhtml(の中のスクリプト)からexample.comのhtmlにアクセスできてしまうので、上で説明したようにその情報を抜き出すことができます。
しかし、実際には同一オリジンポリシーがあるため、trap.example.comとexample.comという別のオリジンのコンテンツ間のアクセスは制限され、クライアントは守られるようになっています。
これが同一オリジンポリシーによって防げる攻撃です。
CORS
CORSの必要性
上で説明したように、別のオリジンから提供されたコンテンツ同士のアクセスは同一オリジンポリシーによって制限されます。
しかし、時と場合によっては同一オリジンポリシーによる制限が邪魔になるケースもあります。
例えば、このブログはmicroCMSというheadless CMSを使って管理しています。
これは同一オリジンポリシーに則ると、このブログの記事はmicroCMS上で管理されているコンテンツにしかアクセスできないということです。
これだとこのブログ記事に貼る画像などもmicroCMS上にないといけませんし、アマゾンの商品説明などにも気軽にアクセスできず、非常に不便です。
そこで、「同一オリジンポリシーを超えて異なるオリジンのコンテンツ間アクセスをできるようにしよう」というのがCORSです。
CORS
上でも説明したように、CORS(Cross-Origin-Resource Sharing)とは「異なるコンテンツ間アクセスを可能にする仕組み」のことです。
CORSの実現はコンテンツ間のリクエストの種類によって次の2種類に分けられます。
- 単純なリクエストの場合:アクセスによる機密情報の漏洩などの心配がないので特に何もしなくてもコンテンツ間アクセスを許可
- 単純でないリクエストの場合:プリフライトリクエストを使って許可されたオリジンからのリクエストかどうかを確認
ここで、「単純なリクエスト」と「プリフライトリクエスト」という2つの概念が出てきました。
以下ではこれらのリクエストについて説明したいと思います。
単純なリクエスト
単純なリクエストというのは以下のような要件を満たすリクエストのことです。
- メソッド:次のうちのいずれか
- GET
- HEAD(ヘッダのみをGETするメソッド)
- POST
- ヘッダーの種類:次のうちのいずれか
- Accept
- Accept-Language
- Content-Language
- Content-Type
- Range
- ユーザーエージェント(ざっくり言うとクライアントのOSとブラウザのこと)によって自動的に指定されたヘッダー(Connection、User-Agentなど)
- Content-Type:次のうちのいずれかのメディアタイプ
- application/x-www-form-urlencoded(urlをパーセントエンコーディングしたもの)
- multipart/form-data(フォームのデータで複数種類のデータをもつもの)
- text/plain(プレーンテキスト)
これ以外にも細かい仕様がいくつかありますが、おおきいところではこの辺になります。
要するに単純なリクエストというのは「HTMLフォームから送られるリクエストと同程度のリスクであるようなリクエスト」であるといえます。
というのも同一オリジンポリシーの目的がXMLHttpRequest(JavaScriptからの非同期な通信)などによるクロスオリジンアクセスを全開放すると危ないので、これを制限しようというものでした。
HTMLフォームからのアクセスで悪さをするのは難しいので、これに合わせているということです。
ただし、この要件によってクロスサイトリクエストフォージェリなどの危険性は高まってしまうので、全く同程度のリスクとはいかないようです。
プリフライトリクエスト
クロスオリジンリクエストが単純なリクエスト出なかった場合、クライアントは自動的にプリフライトリクエストといういわばテストリクエストを送ります。
プリフライトリクエストは次のような内容のリクエストです。
- メソッド:OPTIONS
- パス:クライアントの送り先
- ヘッダー:Access-Control-Request-Method、Access-Control-Request-Origin、 Originなど
- ボディ:なし
Access-Control-Request~系のヘッダーは実際のリクエストがどのようなもの(オリジン、ヘッダの種類など)であればリソース共有を許可するかということを指定します。
また、メソッドにはOPTIONSという変わったものを使います。
これはサーバが受取可能なメソッドの一覧を返すメソッドで、プリフライトにはぴったりなメソッドです。
終わりに
CORSを含めセキュリティ関連のお話は、Web系の勉強をするなら真っ先にやるべき項目だと思ったので今回まとめてみました。
セキュリティ系の話を勉強するとHTTPなどネットワーク系の話も一緒に勉強できるので、大変ですが得るものが多いですね。
今後もセキュリティ系の話題は積極的に上げていきたいと思います。