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 というのもあって、ドメインとドメインのマッピングができます (e.x. myhost.com => example.com => 192.0.43.10) そしたら、 myhost.com へアクセスしたら、 example.com の同じサーバーに問い合わせします。

HTTP の Host ヘッダー

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

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

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

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

解決

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

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 のバージョンを更新しても結局、この問題を解決していない人が出てくるのかなと思いました。

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