2014年03月08日

OpenVPNのセキュリティ周りメモ

OpenVPNのセキュリティ周りの動作について気になった点をメモ

OpenVPN 2.3系、証明書認証を前提。だいたいmanpageから。 https://openvpn.net/index.php/open-source/documentation/manuals/65-openvpn-20x-manpage.html

OpenVPNプロトコル

  • 独自プロトコルで仕様書は見当たらない。ソースを読め。
  • 単一のUDPポート上で制御チャネル(認証など)とトラフィックチャネルを扱う(一応TCPでも動くが遅い)
  • L2イーサネットまたはL3仮想化
  • サーバー側からコントロールチャネルで、ルート設定とDHCP相当を配信可能
  • IPv6ルート通知も可能だが、IPv6なDNSアドレスなどはまだ非対応
  • コネクション毎に仮想ルータとサブネットを分けているため、IPv4アドレス消費はコネクション毎に4アドレス
  • L3モードは内部IPルーティングもできるが、クライアント間の隔離以外にフィルタ機能はないので、必要なら内部ルーティングを無効化してiptablesなりで

セキュリティフロー

  1. セッション開始パケットを事前共有鍵で署名(オプション)
  2. 独自実装のTLS over UDPで、独自ルートCAで認証し、制御チャネルを暗号化
  3. 実際の通信は別の鍵を共有してパケット単位で暗号化・署名

各設定項目はデフォルトで安全よりに設定されている。ca, cert, keyを正しく設定すればしばらくは問題なさそう。

参考 https://openvpn.net/index.php/open-source/documentation/security-overview.html

開始パケットの署名

オプション。 通常OpenVPNサーバーはUDP上で構成され、最初のパケットを処理する際にとてもコストの高いTLS(over UDP)セッションの確立処理が必要になる。 偽装されたUDPパケットを送られるとCPUとメモリを浪費させるDoS攻撃ができてしまうので、最初のパケットには専用の共通鍵を用いたHMAC検証機構がある。 この鍵ファイルは tls-auth オプションで指定する。

ここで使う鍵はプロセス毎にしか指定できず、複数のクライアントがいるなら全てのクライアントで共通になる。この鍵を自動更新する機構はプロトコルには無い。 漏洩時に切り分けと鍵更新が面倒になるので多人数で利用するサーバーでの利用は難しいかもしれない。

TLS over UDP

OpenVPNがUDP上に独自実装したTLS variant(バックエンドはOpenSSLまたはGnuTLS)で、通常のTLS同様に、サーバー検証とクライアント検証を行う。

このTLSで使う鍵交換・暗号・署名アルゴリズムは tls-ciphers で設定する。対応するラベルはopensslのアルゴリズム指定書式に準じていて openvpn −show-tlsで分かる。ひどい命名規則だ。 サーバーは共通のサーバー証明書を(SNIは非対応)、クライアントは個別のクライアント証明書を利用する。それぞれ cert, keyで証明書と秘密鍵を指定。 証明書は ca オプションで指定したCAから認証されているか検証され、オプションで名前(CommonNameだけらしい)の検証と証明書タイプの検証もできる。

証明書の失効はCRL(失効リスト)のみ対応、crl-verifyで有効化。

基本的にクライアント識別やログに記録されるクライアント名はクライアント証明書のcommon nameで行われる。一応一部文字は除去されるようだが、 証明書発行処理を自動化する際はログインジェクションや偽装に注意。 また一つのクライアント証明書を使いまわした複数端末から同時接続も許可できるが、ログが面倒になるのでおすすめしない。

鍵交換はECDHEも指定できるが、曲線の指定方法がない。DHEはdhparamが dh オプションで指定できる。

トラフィック暗号化

トラフィックは制御チャネルで共有した鍵を使って暗号化と署名を行っている

デフォルトの暗号アルゴリズムは Blowfish。 設定ファイル上では cipher で設定する。 鍵長を指定するkeysizeは古いアルゴリズム用なので今は使わずcipherで指定する。 パケット署名はHMACで、デフォルトはSHA1。 auth オプションで指定する。

暗号モード設定は外向きにのみ適用されるので、クライアントとサーバーの両方で個別に設定が必要。

ややこしいことにcipherで署名つきのAES-128-CBC-HMAC-SHA1も指定できる。GCMをつかったAEAD署名はパッチはあるもののまだ取り込まれていない。 利用可能な暗号は openvpn −show-ciphersでわかる。

comp-lzoオプションでLZOパケット圧縮が利用でき、他にもビルドオプションでsnappy、LZ4に対応する。 パケット圧縮を有効にすると、攻撃者がトラフィックを生成することでCRIME攻撃が原理的には可能になる。 ただ圧縮はパケット単位なので、TLS上のCRIME同様同一セッションに攻撃トラフィックが乗る必要がある。ブラウザとVPNの出口側がセキュアであれば問題はないはず。

暗号化パケットはシーケンス番号とsliding window(デフォルトで64パケット/15秒)で管理している。つまり短時間の順序変化と重複は許容され、パケットのドロップや重複は上位レイヤの回復処理に依存している。

no-replayとno-ivオプションがありシーケンス番号とIVを省略できるが、リプレイと暗号文一致攻撃による平文回復(BEAST攻撃と同じ)が可能になるため基本的に有効にしてはいけない。

floatオプションを指定しない限り、各コネクションのリモートIPアドレス・ポートは固定され他のアドレスからのパケットはドロップされる。

トラフィックとクライアント管理

L3モードでは内部ルーティング機能があり、最低限のルート管理とクライアント間トラフィックの分離が可能。

ファイアウォール的な機能はないので、OS側のフィルタ機構を使う。 特にL2モードではARPフィルタなどイーサネットレベルのフィルタリングも忘れずに設定する。

クライアント毎のトラフィックは status オプションで定期的にダンプ可能。IPアドレス割り当ては ifconfig-pool-persist で固定割り当てが可能。

shaperオプションでコネクション当りの外向き速度制限が可能で、client-connectフックを使えばクライアント毎に異なる速度制限を生成することも出来るようだ。ただし内向きの速度制限は見当たらない。

基本的にクライアント別に細かい設定や認証をしたい場合は、フックスクリプト経由で設定ファイルを動的生成する。 どのオプション項目が動的生成に対応しているか記載がないので、個別に検証が必要。

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

2013年09月29日

パスワード認証を脆弱にする10の方法とアンチパターン

いかにしてパスワード認証を脆弱にするか。プログラミング黎明期からずっとデベロッパーの頭を悩ませ続ける問題です。

ここでは脆弱なパスワード認証を実現するための方法を紹介します。

パスワード自動入力の禁止

不届きなブラウザがパスワードを記憶してしまうことがあります。 パスワードは間違いの無いように、ひともじひともじ、人間が入力するべきです。

<input name="pw" type="password" autocomplete="off" />

とするのは常識ですね。ブラウザのパスワード管理機能より、脳内の文字列の方がずっと安心です。

  • フォームを動的生成、AjaxでPOST
  • cursor: textスタイルで偽input

などで、ブラウザのパスワード保存をスキップする方法もあります。

パスワード貼り付けの禁止

貼り付けも自動入力と同罪です。onpaste属性を利用して

<input name="pw" type="password" autocomplete="off"
  onpaste="javascript:function(){return false;}" />

もしくはjavascriptで、onpasteイベントの匿名リスナを登録する方法がメジャーのようです。 他にも、やや面倒だったり環境依存しますが、

  • 画像化された仮想キーボードをクリックさせる
  • onkeydownや透過inputを読んで文字列を構築
  • タイピングの速度を監視する

など、ペーストを防ぐ方法もいくつかあります。 古典的には右クリックを禁止する方法も使われましたが、近頃のブラウザは無視してしまうことが多いので不十分です。

パスワードの長さを制限

javascriptが無効なフィーチャーフォンに対応しなければならないこともあります。

<input name="pw" type="password" autocomplete="off"
  maxlength="8" onpaste="javascript:function(){return false;}" />

のようにmaxlengthは多くのブラウザでサポートされています。

このポリシーを仕様の段階で明確化するため、データベースの生パスワードを保存しているカラムは CHAR(8) と定義しましょう。長すぎるパスワードはRDB側で無視することが出来ます。

入力フォームの値を制限

デバッグチームから、環境やブラウザによっては期待通りに動作しないパスワードがある、なんてレポートが送られてくるかもしれません。 おかしなパスワードが入力されることが無いように、入力の段階でさにたいずしましょう。

$("input").bind("change", function(e) {
    if ($(this).val().match(/[^a-zA-Z0-9.-]|SELECT|DROP/)) {
        alert("不正な文字列");
        $(this).val("");
    }
});

ちょっと長いのでjQueryに頼っていますが、ログイン失敗は致命的なので仕方がありません。 問題が起きそうなパスワードではアラートを出してユーザに適切なパスワードを入力してもらいましょう。

パスワードの再利用を禁止

同じパスワードの使い回しを防ぐために、過去のパスワードは全て保存しておき、変更の際にチェックしましょう。

どんなに古いパスワードでも遡ってチェックできるように、生パスワードをアカウントやメールアドレスと紐つけてデータベースに保存しておきます。万一消えてしまうと本人さえ覚えておらず取り返しがつかないので、DBオペレーターはバックアップをローカルPCにとっておくべきですね。

パスワードの定期的な変更

パスワードは定期的に変更しましょう! 安全[誰の?]のため、数ヶ月に一度は強制的に変更させるべき[要出典]でしょう。 このポリシーはいまや業界標準となっています。

これは同時に、ユーザーに脆弱なパスワード認証の重要性を思い出してもらう良い機会です。 何度も何度もパスワードを考えさせれば、より覚えやすいパスワードを編み出してくれることでしょう。

秘密の質問を設定

パスワードを思い出すのが億劫なユーザーのために、秘密の質問を用意しておきましょう。

質問内容はユーザーに決めさせるより、あらかじめよく検討した質問を選択式にしておくと、忘れにくく間違いが少なくなります。 たとえば親の旧姓や、叔父の名前、卒業した小学校など、指紋同様生涯変化することがないものにするべきです。もしくは、ペットの名前、好きな歌手、乗り換える駅のように、誰でも何度となく耳にする名前を使うことも検討してみましょう。

先ほど生パスワードを保存しておいたので、パスワードを変更することなくメールで送る事ができます。データベース設計を厳格にしておくと、こんな効果もあります。

オペレーターによるパスワードの復旧

パスワードを忘れてしまったユーザーが最後に頼るのは電話のようなアナクロな方法です。人件費が高コスト要因ですが、チャットのできるJavaアプレットやActiveXプラグインを使い、海外にアウトソーシングすれば比較的安上がりです。

ユーザーのアカウント名とメールアドレスがデータベースに入っているか、オペレータが確認してパスワードを口頭やチャットでユーザーに伝えることができます。オペレータにデータベースへのアクセス権限を与え、チェック項目を最小限に抑えておくことで、対応にかかる時間が短くなりコスト削減に繋がります。

ブラウザ・OSバージョンへの最適化

一般ユーザー向けのウェブサイトでは難しくなりつつありますが、IE6とWindows XPといったブラウジング環境を限定するのは、銀行業務など確実性の求められるウェブアプリを実現する方法としていまだ一般的です。第三者によってよく研究されたブラウザと安定したOSに、高度に最適化されたソフトウェアが日々の業務を支えています。

高度に最適化されたアプリケーションは、最新のブラウザのエミュレーション機能(互換モード)によって不正に利用する事が難しくなり、アプリケーションはしばしば難解でプログラマが重要な情報を解読するまでにかかる時間が引き伸ばします。アルゴリズム秘匿によるセキュリティは長い実績に裏付けられています。

変種として、簡易ブラウザ内蔵のスマートフォンアプリを使わせる、という方法もあります。いずれにせよ、ブラウザの解釈やUIを開発者側で制限する事が重要です。

インシデント情報流出を避ける

パスワードなどの情報の流出がおきたという情報の流出を避けるのはとても重要です。 ひとたび情報流出が露見すると、様々な手法で低コストでシンプルかつ脆弱になったパスワード認証が、不安を煽る自称セキュリティ専門家のせいで、複雑で不便で高コストな認証システムへのリプレースを迫られる事になります。

内部で問題が発覚したら、必要な対策が終了するまでは価値ある顧客にのみ電話など安全な方法で連絡し、一般向けの情報は対策完了後にスキャン画像から生成したPDFとして短時間掲示するのが懸命です。再配布を禁じるために意匠と著作権表示を付け、サーバーのrobots.txtを設定してInternet Archiveやウェブ魚拓に取得されることは避けなければなりません。

アンチパターン

脆弱でないパスワードを実装してしまう恐れがある、こんな安地パターンが知られています。

パスワード辞書・リスト

多くの人がよく使われるパスワード辞書に載っているパスワードを使いたがります。これを警告することはユーザーをいたずらに不安にさせます。人は自分は他人と違うと思い込みたがる生き物です。

もっとも、パスワードが辞書やどこからか流出したリストに載っているため、という事実を、定期的なパスワード変更のため、と表現することはマナーの一つなので覚えておきましょう。

ソルト付きハッシュ

生のパスワードを破棄してソルト付きのハッシュを保存していると、オペレータですら忘れたパスワードは復元することが出来ません。アカウントへのアクセスを回復するには、全く新しいパスワードを設定することになります。

同一のソルトを使い回さない限りパスワードを変更しても総当りがヒットする確率は変わらない、という説を信じられない人は多く、しばしば理解が得られません。 ルーレットの同じ目に賭け続ける戦略と、毎回違う目に賭ける戦略では、儲けに大きな差があると感じるのはとても自然なことです。

互換性も心配です。cryptのように、ある程度規格化された方法がありますが、短いパスワードカラムではラウンド数すら入りきりません。

複雑なパスワードよりも長いパスワード

特殊文字を含む8文字より10文字の大小英数字の方が総当り耐性が高いなんて、1000回パスワードを変えるよりも2文字ランダムな英数字を追加する方が強いなんて、にわかには信じられません

特殊文字のエスケープやUnicodeの正規化処理を実装する必要性すらないと言うのでしょうか?

ワンタイムパスワード

せいぜい葉書一枚ですむマトリクス認証と違って、ワンタイムパスワードトークンを購入・送付するコストは洒落になりません。

RFC6238 のようなオープンな実装は、アルゴリズムが公開されているのが心配ですし、サーバーでntpdを動かすなんて面倒です。

リスク検出による再認証・ロック・通知

違う国から連続してアクセスがあったり、突然スパム行為を始めたり、他のサイトから漏洩した脆弱なパスワードが使われている、そんな脆弱さが明白なアカウントが見つかることはよくあります。

セッションを無効化したり、アカウントを一時的に凍結したり、パスワードの変更を推奨する通知メールを送ることは、潜在的なリスクを白日の下に晒してしまいます。

最新のブラウザ

新しいブラウザほど、旧来の仕様が変更されている可能性が高くなります。

右クリック禁止禁止しかり、最近のブラウザではウェブアプリがユーザーインターフェースを制御することが難しくなり、またどのブラウザでも似たような外観・挙動になってしまい、蓄えたノウハウによる差別化が難しくなっています。もっとも、フューチャーフォンやメーカーサポートが打ち切られたスマートフォンなど、まだまだ見せ所は残っているので安心です。

インシデント発生時のパスワードリセット

万一データベースに侵入され、それが露見すれば、アカウントのパスワードをリセットすることになります。 これによって、どれだけのユーザーがアカウントにアクセスできなくなるのか未知数です。特にアクティブなユーザーが少ない場合、致命傷になりえます。

まとめに

簡単にチェックできる手法をまとめたので、既に実践されていて役に立たないという方が多いかもしれません。

もしアカウント認証をスクラッチで実装する機会があったら、ぜひここに挙げたアンチパターンに気をつけて実装してみてください。よりよい認証を実装できるかもしれません。

パスワード認証をより効率よく脆弱にする方法がありましたら、ぜひお知らせください。

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

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月12日

NginxのSSL設定メモ

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

 


 


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

2013年09月11日

BEAST攻撃はクライアント側対策で解決するのか

SSLサーバー設定のテスト目的で使っている人も多いと思う Qualus Security Labsの記事
https://community.qualys.com/blogs/securitylabs/2013/09/10/is-beast-still-a-threat
で、SSL CheckerからBEAST脆弱性の判定(TLS1.0以下でCBCモードを使っている)を外す、というのが有ったのでメモ。

BEASTは成功すると数分から数十秒程度でクライアントのセッションが解読されるため、非常に危険なものと考えられている(orた)。
TLSv1.0以前ののCBCモードでは、セッションを再利用してリクエストを送る際、最後に送信された暗号ブロックが、次のHTTPリクエストの最初のブロックのIVとして使われている。攻撃者が送信済みの最後の暗号ブロックを傍受した後で、リクエストの先頭ブロックにあたる平文、例えばクエリ文字列を変更できるなら、BEAST攻撃が成立する。

最初に考案された対策は、HTTPSリクエストの直前に0バイトのTLSレコードを送る方法。
TLSでは可変長データストリームを扱うために、任意のデータ長を一つのレコードとして暗号化し、レコード毎に署名して送信できる様になっている。少なくとも1つ暗号ブロックが送信されるので、次のリクエストのIVは予測できなくなる。
https://www.ipa.go.jp/security/rfc/RFC2246-06JA.html#621
だが、TLSv1.0では空のレコードを想定しておらず、解釈してくれないTLS実装があった。
(自分の知識はここまでで止まっていた……)

その後、全てのサーバーが対応できる方法として、リクエストの最初の1バイトでTLSレコードを分割してしまう、という方法が考えらた。
https://bugzilla.mozilla.org/show_bug.cgi?id=665814#c59
攻撃者が操作したかもしれない新しいリクエストをTLSで送る際に、最初の1バイトでTLSレコードを分割してしまえば、最初の暗号ブロックで攻撃者が変更できる情報はせいぜい1バイトになる(正確にはレコード長などもいじれそうだが)。そのうえにTLSフラグメントの構造上CBCブロックの残りはMACで埋まる。BEASTが成立するには攻撃者は最初の平文ブロック全体を(ある程度の確率で
制御する必要があるので、攻撃を無効化出来る。代償は、HTTPSリクエスト毎のMACの生成・検証コストが二倍になること。


この1/n-1 splitting手法は、多くのブラウザのTLSクライアント実装に2011年末〜2012年にかけて導入されているようだ。(iOS/Safari除く)
Qualys Security LabsのSSL Checkでは現在、BEASTはクライアント側で対応可能として、RC4や鍵交換をより重要視して評価するように変更されている。

ただ不安な点は、いまだ一定数いるだろう、1/n-1 splitting非対応のクライアントをサーバー側でチェックできていない点だ。(ハンドシェイク中に推測することはできるかもしれないが、opensslにそういったオプションが今のところない)
メーカーサポートが切れたスマートフォンのように、ブラウザがアップデートされなくなった環境では、クライアント側対策が行き渡っていない可能性が高い。日本のように2年毎の契約期間で端末のリプレースが抑えられていると、もうしばらくは2011年製の端末、Android2.3あたりが生き残りそうだ。

個人的には、今すぐセッションIDが解読されたら困るが、将来解読されても困ることが無いウェブアプリなら、まだRC4-SHAでいいように思う。RC4が解読される可能性よりも、XSS脆弱性が残っている可能性の方がずっと高いだろう。
逆にOAuthのように長期間変化しない認証用トークンを利用するなら、TLSv1.2(openssl-1.0.1以降)でサポートされる強い鍵交換と暗号モードを使うようにしたほうがよさそうだ。
ラベル:Beast ssl
posted by ko-zu at 20:23| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする

2013年09月10日

ロリポップレンタルサーバーの被害報告がひどい件+注意喚起

ロリポップレンタルサーバーを運営するpaperboy&co.から被害に関するニュースリリースがでていた
http://www.paperboy.co.jp/news/201309091900

あたかも影響範囲は改竄被害をうけた人だけのように読めてしまい、改竄だけで済ませてくれた愉快犯より前に、他の誰かが悪用目的でアクセスしていた可能性を考慮していない。
少なくとも、同社のサーバーにアカウントを持つユーザーは全て、DBパスワードや、スクリプトの管理ページヘのログインパスワードを変更しておくべきだ。


今回の脆弱性を悪用すると、特段の前提条件なしに犯人以外のだれでも、少なくとも同一サーバーの他のユーザーのファイル・データベース内容を閲覧できた。
ということは、今回の犯人が最初に脆弱性に気づいたのだと確認できない限り、ロリポップレンタルサーバーの(どのサーバーが被害を受けたか明示されていないので)全てのサーバーで、

・被害を受けなかったアカウント含め、DBパスワードやCGI/PHPスクリプトの内容

・DBに入っている、ブログや掲示板のユーザーの通知メールアドレス、ECサイトの顧客個人情報

・アクセスログなどから、サイト閲覧ユーザーのIPアドレスやアクセス時刻

などが漏れていた可能性がある。これらの「可能性が無い」ことを確認しようとすると、少なくとも脆弱性発生時点から修正完了までの、HTTP・DBアクセスログ、アップロードされたファイルなどを精査しなければならない。

件の脆弱性は相当長期間にわたって(おそらくはサービス提供開始当初から)存在していたはずなので、全てのログとアップロードされたファイルのバックアップを完全に保存しているとは考えにくい。どうやって、
『個人情報へのアクセスは無く、個人情報の漏えい、流出の心配はございません。』
などと言い切ることが出来るのだろうか? (現時点で確認されていません、ではない)
念の為、リンクトラバーサルという攻撃手法は今回の件の犯人が発明したものではないし、不法にアクセスした人間が得られた情報を公開するとは限らない。

漏れたことが確認できなくても、漏れた可能性のあるパスワードを使い続けるのは避けるべきだ。
ニュースリリースには脆弱性が発生した時期が記載されていないので、幸運にも被害を免れた(WordPress以外の)ユーザーは全て、最低限DBとCGI/PHPスクリプトの管理パスワードを変更しておく事をお勧めする。

もっとも、過去にデータベースに個人情報を保存していたことがあるのなら、誰かが閲覧していない事を祈るしか無いのだが。
posted by ko-zu at 01:15| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする

2013年09月07日

エンドユーザーによるパーミッション制限に意味はあるか

先日ロリポップレンタルサーバーでの改竄事件についてのエントリを書いた
http://causeless.seesaa.net/article/373409482.html

この事件の後、
・パーミッションはエンドユーザーが"必ず" xxxに制限しないといけない
・シンボリックリンク脆弱性はFollowSymlinksを無効にすればいい
というような事がまことしやかに?言われている様に思われたので書いておく。

エンドユーザーによるファイルのパーミッション制限は、

1. 複数ユーザーが共用するサーバーにおいて、
2. サーバーの非特権プロセスにトラバーサルやそれに類する脆弱性にあったとき、
3. 自分の管理下のスクリプトやパスワード管理手法には全く脆弱性がない

場合には意味がある。 実際、ロリポップレンタルサーバーの件ではroot権限が奪われたわけではなさそうなので、パーミッションを厳しくしてさえいれば難を逃れた人は結構いるのではないかと思う。逆に言えば、2の段階で未然に防ぐことで、ユーザーがパーミッションを全く気にしなくても安全に運用できるレンタルサーバー業者もある

つまりは、その業者の技術をどれだけ信用できるか、による。



まずトラバーサルについて説明したい。
トラバーサルとはアプリケーションのアクセス制御ポリシーの漏れを突く攻撃手法の一つだ。

今回指摘されたApacheのFollowSymlinksは、管理者の意図したアクセス制御、例えば
「各ユーザーの公開ドキュメント用フォルダ配下のファイルにだけ、Apacheがアクセス出来るようにする」
というアクセス制御ポリシーを、エンドユーザーがシンボリックリンクで迂回する方法を提供してしまう。
具体的な方法は徳丸氏の解説が詳しい
http://blog.tokumaru.org/2013/09/symlink-attack.html

この場合Apache(とその開発者)にしてみればソースコードに書かれた通り正しく実行しているだけなのだが、サーバー管理者の意図したポリシーとのミスマッチが起こっている。
トラバーサル脆弱性は、本来意図したファイルアクセス制御ポリシーと、実際に設定されたポリシーのズレを突く手法、ということができる。

トラバーサル可能性を塞ぐ対策は、ファイルへアクセスするアプリケーションが非常に多いためにとても面倒だ。FollowSymlinksを悪用する方法はその一つでしか無い。
サーバーで動作するApacheのコア機能や、mod_rewriteなどモジュール、FTPなど各種デーモンなど、サーバーで動く全てのソフトウェアのすべてのファイルアクセス用コードが、全く同じアクセス制御ポリシーを守るようにしないと、それは何らかのトラバーサル脆弱性に繋がる。

各ユーザーのCGI/PHPスクリプトにトラバーサル可能な脆弱性があっても、普通はそのユーザーが被害を受けるだけですむ。しかし、サーバーの基本的なサービスにトラバーサル可能性があると、攻撃者は僅かな権限を得るだけで、あるいはスクリプトすら用いずに、非常に多くのファイルにアクセス出来るようになってしまう。
パーミッションでの制限は、他のユーザーのトラバーサル脆弱性全てに対する、より低レベルながら一つの防御手段になる。

パーミッションはPOSIX互換OSの基本的な機能で、ファイル所有者(User/Owner)・同一グループ(Group)、それ以外(Others)という3分類のアクセス制限しか提供してくれない。
単純なだけあって、これを破ることは一般に難しい。パーミッション制限を迂回できるような脆弱性や、SuExecのようなユーザーIDを自由に変更できる特権をもつコードに脆弱性があれば、管理者用のコードを書き換えて、サーバー全体を乗っ取る事ができてしまい、同一OSを使っている全世界のユーザーが等しく危険になる。これは特権昇格可能な重大な脆弱性と見なされ、発見されると緊急のセキュリティパッチとして、比較的短期間で修正される(と期待できる。普通は。) 携帯端末のいわゆるJailbreak/root化には、販売後修正されなくなった脆弱性が使われることもある。


もちろん、ユーザーによるパーミッション制限は、ユーザー側ができる最後の自衛手段であって、これに頼る運用はとても不安だ。
サーバーの運用ポリシーにとしては、ユーザーがファイルのパーミッションを全く管理しなくても安全な方法がある。
最新のApacheの厳格なアクセス制限ポリシーや、ユーザー別プロセスのchroot/jail、LXCのようなコンテナ化などを利用して、各ユーザー間を可能な限り安全に切り離して管理していることも多いだろう。

実際に、トップが即座に自社のサービスに同様の脆弱性がないことを説明できるさくらインターネットのような場合もある。(またパーミッションに頼った運用もしていない)
一方で、ロリポップレンタルサーバーのように、トップが即座に自社のサービスに脆弱性がないと言いつつも、指摘されるまで修正できない業者もある。彼らは、事件発覚1週間後の緊急メンテナンス http://lolipop.jp/info/news/4149/
まで、Apacheにトラバーサル可能な脆弱性を残したままだった。(このとき修正された脆弱性も、また別のシンボリックリンクトラバーサルと呼べるもの(ToCToUとも別)だったが、ほとんどの業者には関係ないはず)

ユーザーは何もしなくてもデフォルトで安全、というポリシーが好ましいし、当然そうあるべきだ。だが、完全には技術を信頼できない共用サーバーを、コストの都合上利用せざるをえないなら、他のユーザの脆弱性からの二次被害に対するユーザー側の最後の自衛手段の一つとしてパーミッションを設定しておくことは、幾らかの意味があると思う。
posted by ko-zu at 21:20| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする

オンラインストレージの提供する暗号化はユーザーにとってセキュアではない

データを暗号化して保存している、と謳うオンラインストレージサービスは多い。 ウェブサイトでは直接ファイルを閲覧できず、ユーザーのPCやスマートフォンの内部で暗号化してからサーバーに送るので安全性が高い、と主張している場合もある。

しかし、オンラインストレージの多くはウェブサイトのログインにパスワードを要求したり、ログインするだけでファイルにアクセスできたり、パスワードを忘れた時のためのリセット機能があったりする。こういったサービスでは、サービスを提供する側が、ユーザーの保存したファイルを秘密裏に閲覧することができ、ユーザーにプライバシーはない。強制力を持つ政府機関が命令すれば、サービス提供者はデータを復号して提供せざるをえないだろう。

だれかに見られても困らないデータを置くには便利なサービスだが、多数の個人情報や企業秘密のように、第三者への漏洩その自体が重大なデータを保存する場所としては不適切だ。


多くのオンラインストレージサービスが提供するストレージの”暗号化”では、ユーザーだけでなく、サービス側も暗号鍵を持っている

オンラインストレージサービスのクライアントソフトに入力するのは、基本的にユーザーID(あるいはメールアドレス等)とパスワードだけなので、ファイルの暗号化に利用できる暗号鍵(正確にはファイル暗号化用の暗号鍵をさらに暗号化するための鍵)はこれらを組み合わせたものしか使えない。それ以外の情報は全てサーバー側に置かれている。
なお、DropboxやSkydribeなどではワンタイムパスワードを利用できるが、これは第三者の攻撃を制限するためのものであって、データの暗号化には全く貢献しない。

ユーザーIDやメールアドレスはユーザーへの通知のために平文のまま保存しておく必要があるだろうし、秘密にすることがあまり強く求められているわけではない。従って、暗号鍵となりうる秘密は基本的にパスワードしかない

多くのサービスでは、クライアントソフトはパスワードを直接サーバーに送って、暗号化されたデータと暗号鍵を一緒にダウンロードしている。パスワード付きZIPファイルをパスワードと一緒にメールするようなものだ。

ならば、パスワードをサーバーに全く漏らさないクライアントアプリを使い、かつパスワードに暗号鍵として十分に長くランダムな文字列を入力すれば安全だろうか?

もし、サービス提供者が、「パスワードを忘れたなら、二度とファイル内容を復元することはできません」と明示しているなら、それは例外的に安全かもしれない。暗号鍵を失ったら二度と復元できないのは、脆弱でない暗号の性質としては当たり前だからだ。

実際に、パスワードをサーバー側に全く送信せず、クライアント側で暗号化を完結させている(とされている)ものとしては
SpiderOak https://spideroak.com/engineering_matters
がある。ただしこれも、モバイル向けクライアントアプリは暗号化機能を載せておらず、サーバーにパスワードを渡して復号してもらっているようだ。


パスワードを忘れても、リセットする方法があるならば、パスワードはただの合言葉で、ファイルの暗号鍵ではないことが分かる。ただし、反論としては次のような”安全な”パスワードリセット手法が考えられる。
「パスワード(あるいはファイル保存用の暗号鍵)を、パスワードリセットのために必要なメールアドレスや予備の質問などから生成した鍵で暗号化し、別に保存しておく」
こうすることで、パスワード(か必要な暗号鍵)を復元できる。
公開鍵暗号を使っている人なら、同じ秘密鍵を複数のPINコードで別の場所に保存しておけば、ひとつ忘れても他のPINコードを覚えていればそれが使える、といえば分かるだろう。

だが現実のサービスで、パスワードリセットのための秘密の質問とその答えは、どこで入力しただろうか? オンラインストレージサービスのウェブサイトで入力したのでは、それがログに残るのでやはりサービス側が暗号鍵を得てしまう。(SpiderOakではPWのヒントを表示できるるだけで、秘密の質問とその答えのような仕組み自体が存在しない)


結論として、
・ダウンロードしたクライアントが認証のためにパスワードをサーバーに送るサービス
・パスワードをウェブサイトに入力することがあるサービス
・サービスのウェブサイトでパスワード変更やリセットが可能なサービス
は、サービス提供側と司法当局などによってファイルの内容が盗み見られることがありえる。

著名なところと思われる、Dropbox、SugarSync、Google Drive、SkyDrive、Bitcasa、Copy.comは上記のいずれかに該当する。

ユーザーのデータをサービス側が見ることの出来ないシステムを作るのは面倒だしコストがかかる。
なぜなら安全に暗号化されたデータは原理的に圧縮・重複除去することができない(=乱数列と見分けが付かない)ため、安全でない手法のようにストレージやネットワーク帯域を節約することが出来ない。
また、ファイル内容をサービス事業者側が全く管理できないので、違法なファイルをアップロードしたユーザーがいることが発覚して、FBIにサーバーを全て差し押さえられるような法的リスクも上がる。

使い切れないほどのスペースと根拠なく「暗号化されているので安全」という謳い文句のオンラインストレージサービスを聞いたなら、本当に秘密にすべきデータをアップロードする前に、その仕組を疑ってかかるべきだ。
いまのところ、オンラインストレージ側の暗号化に頼らずに、TrueCryptのようなクライアント側だけで暗号化が完結することを検証可能な暗号化ソフトで正しく暗号化した上でオンラインストレージにおくのが、秘密にしたい大きなデータ同期への現実解になるだろう。 実際、Dropboxのクライアントソフトは、大きな暗号化ファイルの変更部分だけをアップロードしてくれる。(複数人で編集したりすると大変だけれど)


なお、サービス側が暗号鍵を持っていても、ストレージの暗号化は別の面でユーザーのセキュリティに寄与する。
大きなサービスになればなるほど、管理するハードウェアの数も、データに直接触れることが出来る人間も増える。数が増えれば、そのどれか一つを紛失したり悪人が紛れたりする確率は増えていく。暗号化プロセスや、暗号鍵の管理を限られたサーバーとオペレータだけで行うことで、セキュリティ上のリスクは大幅に低減できる。
ただし、これはサービスを第三者から守るセキュリティであって、秘密のデータをサービス自身や政府機関などの覗き見から守るセキュリティではないことに注意する。
posted by ko-zu at 16:48| Comment(0) | TrackBack(0) | セキュリティ | このブログの読者になる | 更新情報をチェックする