2013年11月26日

OpenVZゲストでNative IPv6とIPv6トンネルのデュアルルートを作る

DTIのserversman VPSをx86_64化してIPv6がいくらか動くようになったのでOpenVPNサーバーにしてみた。

前提

  • OpenVZのVPSゲスト、CentOS6.x + OpenVPN 2.3.x
  • dev venet0:0 にNative IPv6が1つ(/128)だけ割り当て済み。可能ならこのルートを使う。
  • He.netのIPv6トンネルから/64の割り当てを受けてOpenVPNのクライアントへ割り当てる。
  • dev tun0 をOpenVPN tun-ipv6 で作成
  • dev tb をユーザースペーストンネル tb_tun で作成。

トンネルとルートの作成

まずOpenVZ上ではカーネルもジュールを導入できないので、sitトンネルを使えない。 トンネルパケットをユーザースペースで処理するtb_tunがあるのでサービスとして起動する。 http://code.google.com/p/tb-tun/

setsid tb_userspace tb {{tunnel_server}} any sit > /var/log/tunnel.log 2>&1 &

tbにIPv6アドレスを割り当て。

ifconfig tb up
ifconfig tb inet6 add {{tunnel_client_ipv6}}

venetをデフォルトにするためmetricを増やして追加しておく。

ip -f inet6 route add default metric 10 dev tb

他のOpenVZマネージャ(SolusVM)ではIPv6割り当てが無いのにデフォルトルートが設定されている。その場合削除しておく。

ip -f inet6 route del default metric 1 dev venet0

iptablesで-p 41のINPUT/OUTPUTを通しておく事を忘れずに。

iptables -A INPUT -p 41 -s {{tunnel_server}} -j ACCEPT

ここまででトンネルIPv6は到達可能になった。

トンネル-VPN間のルーティング

IPv6ルーティングを有効化

net.ipv6.conf.all.forwarding = 1

He.netを経由させるRoutedなソース範囲に対してルール付きのテーブルを作成、デフォルトルートに指定

ip -f inet6 rule add src {{tunnel_routed}} table 10
ip -f inet6 route add default metric 1 dev tb table 10

ip6tablesのFORWARDを許可

ip6tables -A FORWARD -i tun0 -s {{tunnel_routed}} -j ACCEPT
ip6tables -A FORWARD -i tb   -d {{tunnel_routed}} -j ACCEPT

ServersMan@VPSはopenvzカーネルが古すぎてip6tablesのconntrackが機能していない。 常用するならもっとまともなフィルタルールが必要。

OpenVPN側

routedなIPv6を割り当てる。クライアントにはデフォルトルートとしてpushする。

server-ipv6 {{tunnel_routed}}
push "route-ipv6 ::/0"

残念ながら、openvpn 2.3.6ではまだIPv6 DNSサーバーアドレスをpushすることができない。 そのためAAAA filteringされていないHe.net提供のIPv4 DNSサーバーを指定するか、クライアント側で上書きしてもらう必要がある。

ひとまずこれでnativeのIPv6を生かしたままIPv6 over OpenVPNが出来た。

ラベル:VPS OpenVZ VPN IPv6
posted by ko-zu at 20:02| Comment(0) | TrackBack(0) | サーバー | このブログの読者になる | 更新情報をチェックする

2013年11月20日

Ansibleのtemplateにカスタムフィルタを追加する方法

Ansibleのtemplateモジュールではjinja2が使われているため、カスタムフィルタを登録して好きな処理を書ける。

templateはplaybookのパース時にも使われるので、Ansibleロード時点でグローバルなプラグインとして導入する必要がある。今のところロードするフィルタの切り替えはansible.cfgで切り替えるしか無いようだ。 (ドキュメントには ./libraryに置くことでロードできるらしいのだがcfgで上書きされているっぽい)

フィルタの登録はFilterModule().filters()が返す辞書。

import socket
class FilterModule(object):
    def filters(self):
        return {"A": socket.gethostbyname}

このファイルを適当な名前でfilter_pluginsフォルダに置くとansibleロード中に読み込まれる。

実際のパス指定はansible.cfgの

filter_plugins     = /usr/share/ansible_plugins/filter_plugins

参照: https://github.com/ansible/ansible-examples/blob/master/language_features/filter_plugins/custom_plugins.py

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

2013年11月11日

Robots.txtチェッカーを作ってみた

http://forbiddenrobots.usb0.net/

Googleのrobots.txtとmetaタグ仕様 https://developers.google.com/webmasters/control-crawl-index/docs/robots_txt に準じているつもり。

Flask+uwsgi-emperor+MongoEngineを試すのがメインだったので、 コアはFlask、サーバーはnginx+uwsgiでuwsgi-emperorでプロセス管理、バックエンドにMemcachedキャッシュとMongoEngine。 httpにpython-requests、htmlパーサはBeautifulSoup4という普通な構成。

ひとまず動くところまで出来たけれど、gevent化やmongodbの分散とか実装してみたい。 いまだにgeventをwindows環境で動かすことができていないのでしばらく掛かりそうだけど。

posted by ko-zu at 23:58| Comment(0) | TrackBack(0) | Python | このブログの読者になる | 更新情報をチェックする

2013年10月19日

CentOS6.4+NginxでECDHEを有効にしてForward Secrecy取得までのメモ

昨日は鍵共有を甘く見て爆死したためCentOS+NginxのサーバーをWindows環境に対応させてみる。

RPMに慣れていた都合上、主なサーバーにCentOS6を使っている。 CentOS/RHEL6.4では2013年10月時点で、標準のOpenSSLは1.0.0、Nginxは1.0系が導入される。Nginxには公式リポジトリもあるが、openssl-1.0.0を前提にビルドされている。

これらはコミュニティサポートのあるRPMで提供されているものの、NginxでHTTPSサーバーを建てようとすると、TLSはv1.0 まで、対応cipherはDHE-RSA-AES-SHAまでになり、QualsysのSSLテストでは色々と指摘される。

問題点

既存のパッケージで対策不能なのは、TLS1.2対応(兼BEAST対策)とRSA鍵共有。

BEASTは未対策のブラウザがTLSv1.0以前のCBCモードで通信する場合に発生しうる。一時的な対策としてストリーム暗号のRC4が使われているが、TLSv1.2ではCBCモードの問題が解消されているためTLSv1.2で導入された暗号モードを使えば問題は無い。しかし、TLSv1.2はopenssl-1.0.0には入っていない。

将来解読される可能性がある(RSA)鍵共有を使わない、というForward Secrecyは各所で既に行われている。RSA鍵共有に替わるものとして、多くのブラウザはDH鍵共有・ECDH鍵共有が実装されている。 CentOSのopensslでも有効なDH鍵共有を利用できればいいのだが、クライアント側、例えばWindows+IE環境ではDHE-RSAが無いためにRSA鍵共有が優先され、Forward Secrecyを保証できないことをご指摘頂いた。(Win7+IEでブラウザのCipherテストみるとDHE-DSSは実装されているのにDHE-RSAが無い)

Windows+IEに対応するためには、ECDH鍵共有を有効にする必要がある。Googleなど多くのサービスは既にECDHE-RSAを有効にしている。

ビルド手順

TLSv1.2と、多くのブラウザでRSA鍵共有以外を使おうとすると、OpenSSLとNginxをリビルドする必要があった。

システムのopensslをいじるのでVMなどのビルド用サンドボックス環境で要検証。

まず、1.0.1のopenssl-develをビルド環境にインストールする。 CentOS6でのopenssl-1.0.1e のビルド手順は http://www.ptudor.net/linux/openssl/ がとても詳しいのでそのまま。 生成されたRPMをインストールしてopenssl version が1.0.1eになり、openssl cipher -v 'HIGH' でKx=ECDHが含まれるかチェックしておく。

Nginxはmainline 1.5.6のSRPMをリビルド。自動インストールするのであれば、nginx.specファイルでRequires: openssl >= 1.0.1e のバージョンを指定しておく。 出来たバイナリはopenssl-1.0.1eのcipherに対応しているはず。 例えば

  ssl_protocol TLSv1.2;
  ssl_ecdh_curve secp384r1;
  ssl_ciphers EECDH+aRSA+AESGCM;

が通るか確認。

実際のciphers指定

次のように明示しておく。

    ssl on;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    # RC4無し
    ssl_ciphers EECDH+HIGH:EDH+HIGH:HIGH:+3DES:!RC4:!MD5:!aNULL:!eNULL:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5;
    # RC4有り
    # ssl_ciphers EECDH+AESGCM:EECDH+SHA384:EECDH+SHA256:EECDH+RC4:EECDH+HIGH:EDH+AESGCM:EDH+SHA384:EDH+SHA256:EDH+RC4:EDH+HIGH:AESGCM:SHA384:SHA256:RC4:HIGH:+3DES:!MD5:!aNULL:!eNULL:!LOW:!EXP:!PSK:!SRP:!DSS:!KRB5;
    ssl_ecdh_curve prime256v1;
    ssl_dhparam /path/to/dhparam2048.pem;
    ssl_certificate /path/to/server.crt
    ssl_certificate_key /path/to/private.key
  • ECDHE>DHE>RSAを明示する。OpenSSLのデフォルトのソートは、ソート順がEnc→KxなのでKxが交互に並んでしまう。
  • 将来証明書がRSAからECDSAに切り替わった時のために、grep Au=RSA、grep Au=ECDSAで順序が変わらように並べる。
  • GCM>SHA-2>RC4>SHA-1なのは、SHA-2未対応のクライアントはTLSv1.0である可能性があり、BEAST対策なされていないかもしれないから。
  • MD5はSSL3.0ならSHA-1必須なので抜く。
  • RC4をそのまま抜くとWinXP+IEで接続できなくなるので、cipherの最後に+3DESを追加する。
  • EC関数のsecp384r1は互換性から減点される模様
  • ECDHは256bit以上、DHは2048bit以上が推奨されている。Nginxのデフォルトは1.5.6でもDH1024bitのままだった。
  • !aNULL以降は表示されるcipher削減のため。利用するクライアントは存在しないかそれ自体脆弱と考えていい
  • 性能上の問題は無視している。

これでほぼ全てのブラウザで対応できたはず。

Forward Secrecy     Yes (with most browsers)   ROBUST (more info)

レポジトリ?

他人のビルドしたopensslを試す無謀な方は

http://yumrepo.usb0.net/GPG-KEY
pub   4096R/848C083D 2013-11-17
      指紋 = D11E 88FF 3FC0 4339 EE1D  BBA4 4F1E A7EE 848C 083D
uid                  causeless (myrepo sign key) <causeless@gmail.com>
[myrepo]
gpgcheck = 1
enabled = 0
name = My Repo - $basearch
baseurl = http://yumrepo.usb0.net/RPMS/$basearch/
[myrepo-noarch]
gpgcheck = 1
enabled = 0
name = My Repo - noarch
baseurl = http://yumrepo.usb0.net/RPMS/noarch/
[myrepo-source]
gpgcheck = 1
enabled = 0
name = My Repo - noarch
baseurl = http://yumrepo.usb0.net/RPMS/source/
ラベル:ssl nginx CentOS
posted by ko-zu at 19:11| Comment(0) | 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) | セキュリティ | このブログの読者になる | 更新情報をチェックする
×

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