Engineer in Tokyo

Django 1.3.1 セキュリティリリース

先週の金曜日にDjango 1.2.7とDjango 1.3.1のセキュリティリリースがリリースされました。いくつかのセキュリティ問題が1.2と1.3系のDjangoにありました。なるべく早くアップデートすることをおすすめします。

僕の会社の AE35 さん リリースを翻訳してくれたので、よければお読みください。セッション操作とURLFieldの問題がありますが、基本的に1.2.7 か、1.3.1に更新したら、直りますので、みなさんははまらないと思います。

けど、ホスト名の処理の問題もあります。すこし分かりにくいと思うのでホスト名の問題についてもうちょっと詳しく絞って書きたいと思う。ホスト名の問題は単にアップするだけで直るわけではないので、すこしウェブサーバーの設定が必要です。

DNS の仕組み

ます、問題の説明。ブラウザからウェブサイトをアクセスした時に、普段はドメイン名でアクセスします。そのドメイン名はどのIPアドレスなのか?とDNSサーバーに問い合わせします。

例えば、host コマンドに example.com 渡したら192.0.43.10が返ってきます。

$ host example.com
example.com has address 192.0.43.10

いくつかのドメインは同じIPアドレスにマッピングできます。そして、CNAMEというのもあって、ドメインとドメインのマッピングができます(例:myhost.com => example.com => 192.0.43.10)。そしたら、myhost.comへアクセスしたら、example.comの同じサーバーに問い合わせします。

HTTP の Host ヘッダー

ブラウザがウェブサイトへアクセスする時にHostヘッダーがHTTPのリクエストに付いてきます。このホストヘッダーはサーバーで決める値ではなくて、クライアントで決めるものなので、サーバーに送る値が理論的に何でもできます。さして、CNAMEで完全に別のホスト名にすることができます。

例えば、Aさんがwww.example.comをつくりました。僕はwww.mydomain.comCNAMEを作って、www.example.comと同じIPアドレスに解決するように設定できます。ようするにAさんが知らないで、www.mydomain.comを見るとwww.example.comに問い合わせして、www.example.comが表示される。

それはそれでいいんですが、Djangoのrequest.get_host()がブラウザで送ったX-Forwarded-HostHostの順番でヘッダーに入っている値を使うので、もしそのホストで絶対URLを作成してると、知らないHOSTがサイトのHTMLに入ってくる。それでページをキャッシュしている場合だと、「cache poisoning」が行う。ようするに、僕が送った値によって、僕が設定したwww.mydomain.comのURLがキャッシュに入ってしまって、他のユーザーがそのページを見て、リンクをクリックすると別サイトに飛んじゃう。

つまり、X-Forwarded-HostHostヘッダーを信用してしまっているわけです。

解決

Hostヘッダーは使えないので、サイトのドメインを信用できる方法で取得しないといけません。Django 1.3.1でUSE_X_FORWARDED_HOSTという設定が追加されて、デフォールトがFalseになりました。これは、X-Forwarded-Hostが設定された場合、それをホスト名として使う。X-Forwarded-HostHostヘッダーと同じヘッダーなので、自分のウェブサーバーで設定するつもりで利用します。

Nginxの場合:

server {
    ...
    proxy set header X-Forwarded-Host $host;
}

でも、設定に入っていないホスト名でサーバーにアクセスした場合、nginxや、Apacheがデフォルトで最初のヴァーチャルホストにマッチするので、設定していないのにwww.mydomain.comでアクセスできちゃう。自分が設定したドメイン意外なホストでアクセスできないようにするには、キャッチオールを設定しないといけない。ようするに、設定した意外なサーバーにマッチするヴァーチャルホストを設定しないといけない。

Nginxの場合:

http {
    ...

    # Default Server
    # Sites can only be accessed by their given domain names.
    server {
        listen 80 default;
        return 403;
    }
}

これをデフォールトヴァーチャルホストとして設定して、無知なドメイン名でアクセスした場合、このヴァーチャルホストにマッチする。そして、403 Forbiddenを返す。

どのウェブサーバーを使っているかによって、対応が変わるので、Django 1.3.1のリリースノートに具体的に何をすればいいが書いていない。そのため、HTTPとDNSに詳しい開発者はすぐわかると思いますが、ウェブプログラマーの初心者、もしくはDjangoの初心者に分かりにくくて、Django のバージョンを更新しても結局、この問題を解決していない人が出てくるのかなと思いました。

僕が説明する能力ももしかして、足りないかもしれないし、もし、質問か何かがあったら、僕はできる限りヘルプしますので、お気軽にコメントしてください!