2013年09月28日

SSL中間CA証明書の確認方法メモ

先日、久しく寝かせていたレンタルサーバーを使おうと思い、メールアカウントを設定したのだが、SSL通信に失敗した。原因はよくある、中間証明書の設定ミス。POPS/SMTPSサーバーにインストールされた証明書セットが間違っていた。

HTTPSの設定確認はQualys Labのチェックツールが日本語化されていてわかりやすい。ただ、任意のポートを楽にチェックしてくれるサービスが見つからなかったので、opensslを使った方法をメモしておく。

opensslによるチェック

コマンドは

$ openssl s_client -connect app.usb0.net:443 -showcerts < /dev/null
CONNECTED(00000003)
depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 O = AlphaSSL, CN = AlphaSSL CA - G2
verify return:1
depth=0 OU = Domain Control Validated, CN = *.usb0.net
verify return:1
---
Certificate chain
 0 s:/OU=Domain Control Validated/CN=*.usb0.net
   i:/O=AlphaSSL/CN=AlphaSSL CA - G2
 1 s:/O=AlphaSSL/CN=AlphaSSL CA - G2
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEmzCCA4OgAwIBAgISESF/E/8j4rVO8kDXSzgoA5hEMA0GCSqGSIb3DQEBBQUA
略

最初の depth=X はサーバーの証明書を0とした、opensslのバリデータが辿った証明書の深さ。 2013年現在、普通のウェブサービスが使う安価な証明書なら、中間CAがルートCAとの間に1段挟まれるので、depth=2が OU = Root CA になっている。

次のCertificate chainとServer certificateはサーバーから送られて来た証明書。 通常、サーバー自身の証明書 (ここでは、 CN=*.usb0.net) と、中間証明書 (O=AlphaSSL/CN=AlphaSSL CA - G2) が、送信されている。

サーバーに正しく中間CA証明書とサーバー証明書がインストールされていて、対応するルートCA証明書がシステムにインストールされていれば、

    Verify return code: 0 (ok)

となる。

ただし、アップデートの速いブラウザやディストリビューションは中間CA証明書も信頼済みリストに含めている場合があるので、中間証明書が正しく提供されていなくても、Verifyに成功してしまうことがある。 ルート証明書(issuerのOU = Root CA)以外の証明書全て、サーバーにインストールされクライアントに提供されることを確認する。

中間CA証明書の期限切れ

サーバーID証明書よりも中間CA証明書の方が期限が長い場合が多いので普通は気にする必要が無いが、証明書更新の際に一緒に中間CAも更新することを忘れずに。

不要なルートCA証明書

しばしばルートCA証明書を含む不適切な設定のサーバーがあるが、ルートCAの証明書ををサーバーから送る必要はない。証明書を送る帯域がコネクションあたり1KBくらい無駄になる。

特に、間違ったルートCA証明書(テスト用認証局など)を送信している場合は、HTTPSをよく理解していないユーザーを危険に晒す事になるので、直ちに削除する必要がある。

参考

ラベル:openssl ssl
posted by ko-zu at 22:28| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする

2013年09月24日

DjangoのドキュメントがSession Fixationに脆弱な件

Djangoフレームワークのドキュメントを信用しない、大抵の開発者には無関係な話。


PythonのウェブフレームワークDjangoは他のフレームワーク同様、cookieによるセッション機能が実装されている。 セッションのハイジャック手法の一つにSession Fixation攻撃があり、対策としては、セッションIDに紐ついた権限の変更(または昇格)時にセッションIDを再生成するのが一般的と思う。

DjangoのSession周りのドキュメントにも、Session Fixation対策が示唆されているのだが、あたかもフレームワーク側で対策済みかのように書かれている。

In order to prevent session fixation attacks, sessions keys that don’t exist are regenerated:

しかし、有効なセッションIDは攻撃者が普通にウェブアプリにアクセスすれば手に入るので、存在しないIDを無視するだけではFixation対策にならない。これではサブドメインやディレクトベースでアカウント管理される共用サ―バーのようにFixationが起こせる条件下では、攻撃者は誘導されてログインしたユーザーの権限を乗っ取ることができてしまう。

実際にDjango 1.5.2で試すと、セッションミドルウェア実装はセッションオブジェクト(dict)を明示的に変更しても、IDが変化しないようだ。安全よりも性能側に振ってあるのだろう。

自前でIDの再生成を実装しているユーザーには全く影響しないが、ドキュメントをチュートリアルとしてそのまま実装すると、Session Fixationに脆弱かつ可搬性の高いコードが出来上がることになる。 ちなみに、Django組み込みのadminアプリはログイン時にIDを再生成しているので問題はない。ただ、セッションIDを明示的に再生成するAPIが見当たらない……

個人的にはセッションの再生成はフレームワーク側でサポートするべきだと思う(セッションストアの更新時にIDも変更するだけ)し、少なくともドキュメントは正しく防御方法を示すべきだ。再生成による性能劣化が問題なら再生成の必要がないことを明示するAPIを用意すればいい。

PHPのStrict Session関連でもそうだが、存在しないIDを無視するという“Fixation対策”は英語圏でも蔓延しているのだろうか?

posted by ko-zu at 20:44| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする

2013年09月18日

レスポンス比較によるDNS Anti-spoofing

Twistedの勉強がてら、並列にN回(以上)クエリをして、結果がTTLを除き一致することを確認するスプーフィング対策を考えてみた。 (商用DNSアプライアンスとかでは既に実装されてたりするんだろうか?)

前提

現在の主なDNSのspoofing手法は、リゾルバの使うUDPソースポート番号やクエリ先ネームサーバーを様々な方法で予測あるいは強制して、クエリIDがヒットするまで偽のパケットを送り続ける。 ポート番号のようなDNSの外側に付加された追加エントロピーを攻略されると、DNSパケットのクエリIDは16ビットしか無い。しかもしばしば攻撃者はブラウザやメーラー経由で能動的に好きなタイミングでクエリを発生させることができる。そのため、DNSSECなどで保護されていないDNSレスポンスは比較的短時間で攻撃成功率が現実的になる。 そのため、0x20のような追加エントロピーや、攻撃を検出してTCPで問い合わせる、といった方法があるようだ。

クエリIDの長さが足りないのなら、複数回クエリして、その全てが同じ内容であることを確認すればいい。N個のレスポンスを比較すれば、その全てのパケットが擬装されている可能性は2^(-16*N)になる。 つまり、N個の相同なレスポンスを得るまでリゾルバ側がクエリをリトライする。(あるいはTCPにフォールバックする)

以下の場合、クエリIDを長くすることでスプーフィングを防ぐ事ができる。

  • リモートのDNSサーバーは信頼出来る
  • リモートまでの経路は確率的には信頼出来る
  • 以下の場合は対象外
    • 通信経路が攻撃者に掌握されている(認証されていないWLANやホームルーター、経路ハイジャックなど)
    • ネームサーバーが攻撃者に掌握されている
    • DNSクエリIDが予測可能
    • IPフラグメントが予測可能(恒常的な2ndフラグメンテーションハイジャック)
    • TCPシーケンスが予測可能

利点

  • クライアント側実装ですむ。サーバーは変更なし
  • 攻撃パケットを監視する必要がない
  • DNSSECに比べれば軽い

欠点

  • ラウンドロビンでレコードが随時変わる場合には組み合わせが発散するので、相同かチェックできない。
    • この場合通常のTCP resolverにデグレードしてしまう。
  • UDPクエリ負荷がN倍程度になる
  • エラーレートと遅延が大きくなる
  • TCPクエリが増える

ラウンドロビン

ほとんどの場合ではN回のリクエストで済んでしまうが、AnycastやGeoDNSやラウンドロビンなど、レスポンスRRsetがそもそも変化する状況では、リクエスト数がより大きくなる。たとえばN=2として所要パケット数は、レスポンスの組み合わせの平方根程度になる。

TCPへのフォールバックを使わない対処法としては、レスポンス全体ではなく、リソースレコード単位で比較して、共通部分のみをレスポンスとして再構成する方法も考えられる。 ネームサーバーがm>>1種類のリソースからk個のレコードをランダム抽出してラウンドロビンしていると仮定すると、m!/k!大雑把にm^k通りのレスポンスが得られ、2回一致するには(m^k)^(1/2)程度のリクエストが必要になる。一方、個々のRR毎に二回一致すればいいのであれば、 (m/k)^(1/2)程度で一つは共通するRRが見つかる。 リゾルバが得られるRRsetが変化してしまうのでRFCに反するが、少なくともA/AAAA/NSレコードではリゾルバはそのうちの一つを任意に選んで使って良いので実用上の問題にはならない。(RRSetの完全性が必要なタイプは個別に実装するか、DNSSECを使う) このRR単位で比較するdegradedな場合、だいたい4回クエリすれば、 k*4^2サーバー程度のRRラウンドロビンから共通のレコードを探すことが出来る。たとえばdig google.com A は11レコードを返すので、背後でバランスされているIPアドレスが160程度なら4〜5クエリで解決出来ると期待できる。

多段のCNAME

一番の問題になりうるのは多段階のCNAMEでバランシングされている場合で、c段CNAMEの連鎖が発生している場合、その全てに一つずつレスポンスを得るためにはm^(c/2)のオーダーのクエリが必要になってしまう。この手法を取り入れようとすると、実質毎回TCPでクエリすることになり効率が悪い。

実際には局所性を高めるために(GeoIPなど)、クエリソースIP範囲に対してある程度決まったレスポンスを返すだろうから、極端に悪くなることはなさそうだが。

TTL

この方式でもレスポンスのうちTTLは攻撃者が1/2^16で操作可能になる。 ランダムにレスポンスの一つからTTLを採用してしまうと、極端に短いTTLによるDoSと、極端に長いTTLによるDoS・リバインディングを排除できない。

短いTTLでのDoSは単にリトライすれば(RR内容の改竄はこの検査を通らないので)若干の遅延だけで解決できるので、検査したレスポンスのうち最小のTTLを採用してしまうのがよさそう。

負荷

ネームサーバー側の負荷はリトライ数倍になるが、上限リトライ数を数回でキャップするのは通常のクエリと同じなのと、Androidのようにブラウザの同一ページ並列ロードですらキャッシュせずパラレルにクエリする実装もあるのでこの方法自体がDoSとみなされることも無いだろう。BINDのRRLでリミットされていても遅延をかけたリトライなら成功する。(クライアント側が攻撃対象のDoSなら防ぎようがないが)

truncateされないクエリに対してもTCPでクエリする場合が増えるので、TCP負荷は増加する。今のところ(truncateの必要のない)TCPクエリをrefuseするネームサーバーは少ないだろうが、ルートネームサーバのように問題になるかもしれない。 最悪の場合、TCPを使わず標準準拠なUDPクエリを(と透過な最後のリトライ)採用することになる。

スタブリゾルバ実装

Twistedのスタブリゾルバ実装をモンキーパッチしてminimalなフォワーダを実装してみた。並列クエリとリトライのみで、RRsetの編集やTCPへのフォールバックは実装していない。 (フルリゾルバ出口用にUDPプロキシとして書きなおしているけれどいつになるやら……)

N=2で通常通りのブラウジングしてみたところ、8.8.8.8にフォーワードした場合で大雑把に5%ほどの追加クエリが発生した。

retrystats: [1100, 42, 5, 1, 2] #リトライ数0からインデックスした配列

リトライはだいたいTTLが短い上に複数のCDNを使っているplatform.twitter.com

自分で運用している単独のリゾルバ相手なら(キャッシュテーブルが一つなので当然だが)キャッシュが消える瞬間以外エラーはでていないようだ。Unboundの自動リキャッシュ機能を使えばゼロに出来るだろうか?

少なくともこの方法でリモートのリゾルバとローカルの間でspoofingを受ける心配はなくなると思われる。

ラベル:DNS
posted by ko-zu at 22:49| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする

2013年09月17日

NginxのSSL設定メモ(obsolute)

このssl_ciphersだとWindows+IEでRSA鍵共有が優先されてしまう問題が有ったのでobsolute

 

理由が記載されていない設定例しか見当たらなかったのでメモしておく。2013/9時点の理解。 Apacheでも名前の読替えで全て同じ設定が可能。

    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers AESGCM:SHA384:SHA256:@STRENGTH:RC4:HIGH:!EXP:!LOW:!MD5:!aNULL:!eNULL:!KRB5:!PSK:!SRP;
    ssl_prefer_server_ciphers on;
    ssl_stapling on;
    add_header Strict-Transport-Security max-age=31536000;
ssl_ciphers
  • RC4 > CBC (BEASTの方が重大と考える)なら、上記の通り。
  • CBC > RC4 (RC4の危殆化が重大と考える)なら
      ssl_ciphers HIGH:!MD5:!aNULL:!eNULL:!EXP:!KRB5:!PSK:!SRP;
  • クライアントの構成から、SSLv3とRC4を無効にできるなら、
      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
      ssl_ciphers HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!KRB5:!PSK:!SRP;

ciphersの記述を細分すると、

AESGCM:SHA384:SHA256:@STRENGTH

TLSv1.2以降で導入された方式をMACでリストアップして、暗号強度順に並べている。これらに対応しているクライアントはTLSv1.2で接続しているはず。デフォルト有効ではないものの、メジャーブラウザは実装済み。 なお、

TLSv1.2:!eNULL

でも同じ結果になるはずだが、1.0.1eより前ではTLSv1.2という一括指定は使えないようだ。

RC4:HIGH:!EXP:!LOW

BEAST未対策かつTLSv1.2非対応の古いブラウザ向けにRC4-128bitを優先する。米国輸出規制対応の弱いRC4は!EXPで排除される。(64bitのRC4が実装された時のために!LOWをいれておく) RC4が無効化されていればそれ以外に128bit以上の暗号を使う。

!MD5

今のところ弱衝突はでていないが、MD5が必須なのはSSLv2以下のブラウザだけなので無効にして構わない。SHA1に非対応のブラウザは流石に無いと考えていい。

!aNULL:!eNULL:!KRB5:!PSK:!SRP

署名なし、暗号化なしは無効。また古い形式と事前共有など。使われることは無いだろうが ssl_prefer_server_ciphers on なので念のため無効にしておく

これらのルールは

    openssl ciphers -v 'ciphersuites'

で利用される順序が確認できる。

ssl_session_cache

トラフィックが少ないのでワーカー毎キャッシュを無効にして、全て共有。

ssl_stapling

OCSP応答をサーバー側にキャッシュする。最初のセッション確立までの遅延を減らせる。nginxの場合、resolverを明示的に設定が必要

Strict-Transport-Security

同じくトラフィックが少ないのでブラウザにSSLの利用を強制する。

ラベル:nginx ssl Beast
posted by ko-zu at 19:45| Comment(0) | TrackBack(0) | サーバー | このブログの読者になる | 更新情報をチェックする

2013年09月12日

NginxのSSL設定メモ

次の記事でMarkDownで書きなおしてみた

 


 


古いエントリ
ラベル:ssl nginx
posted by ko-zu at 22:49| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする
×

この広告は90日以上新しい記事の投稿がないブログに表示されております。