nginxのssl_trusted_certificateメモ

nginxのssl_trusted_certificate(およびssl_client_certificate)は異なるコンテキストから利用されている。

クライアント証明書を検証する際に信頼する証明書は ssl_trusted_certificate で指定されるが、この命令で定義されるtrust storeはOCSP Staplingの応答の検証を ssl_stapling_verify で有効にした時にも利用される。

すなわち、nginxでは同一セクションでクライアント証明書の検証とOCSPの検証を同時に有効にすることはできない。

クライアント証明書のtrust store

TLSではクライアント(つまりウェブブラウザ)が証明書を提示して認証とすることができる。

クライアント証明書は通常、ユーザー認証の代わりに使われプライベートな認証局を立てそこからクライアント毎に発行する。 StartcomのSSLS/MIME証明書を取得した経験のある人はブラウザに入っているはず。

nginxではssl_verify_clientで検証を有効にすることができ、ssl_trusted_certificateで検証コンテキスト、OpenSSLでいうtrust store、となるCA証明書リストを設定できる。

ここで設定する証明書リストにはユーザーに対して証明書を発行したCAだけを含めることになる。

OCSP Staplingでのtrust store

OCSP Staplingとは、証明書の失効状態をクライアントが認証局のサーバーに問い合わせるOCSP(RFC6960)の応答を、サーバー側でキャッシュしてクライアントに渡すTLSのオプション。

OCSPの応答にはCRL(証明書失効リスト)同様認証局側の署名が付いているのでTLSサーバーがプロキシ・キャッシュしても問題ないが、OCSPサーバーとの通信は平文なので改ざんされている可能性があり、有効でない(署名検証に失敗する)OCSPレスポンスをキャッシュさせられるとパフォーマンスの劣化が生じうる。

考えうる最悪のケースとしてクライアントの(不適切な)実装によって、不正な署名が含まれるとして接続そのものを拒否してしまうかもしれない。

nginxではssl_stapling_verifyOCSPレスポンスをクライアントに渡す前に検証することができるが、ここで署名の検証に使うtrust storeにssl_trusted_certificateが共用されている。

OCSPの検証はサーバー自身の証明書の信頼チェーン上位、通常は公的に信頼されたパブリックなCAから署名されていることを検証する必要があり、プライベートなCAから発行する事が多いクライアント証明書とは検証コンテキストが異なる。

結論

ssl_trusted_certificateが共通な以上、コンテキストが異なるクライアント証明書検証とOCSPレスポンス検証は同時に使えない。

こんにちのウェブでクライアント証明書を使うことは稀なので問題になるとは考えにくいものの、複数のコンテキストでtrust storeを一つで済ますというのはバグと運用ミスの元になり設計として好ましくない。TLS関連のコードを書くなら他山の石としたい。

なおproxyモジュールなどは、バックエンドとの通信でTLSでリモートを検証する際にproxy_ssl_trusted_certificate のように個別のtrust storeを参照するため、影響はない。(nginx 1.7.8以降)

OpenVZの脆弱性(CVE-2015-2925 bind mount/simfsコンテナからの脱出)について

日本語の情報が無かったようなのでメモ。

2015年4月にbind mountによるコンテナ脱出に利用可能な脆弱性 CVE-2015-2925 が公表され、OpenVZもカーネルパッチを二ヶ月遅れながら出しています。

念のため、これは一年前(2014年6月)に公開された CVE-2014-3519 と同様にコンテナ脱出可能な脆弱性ですが別件です。前回はopen_by_handleのコンテナ対応漏れでしたが、今回はbind mountからのトラバーサルでの脱出を利用したものです。

この脆弱性を悪用するとコンテナ外のファイルシステムにアクセスできるため、同一マシン上の他のコンテナやホストシステム自体をも操作できる場合があり、最近影響範囲の広さから話題となったVENOM脆弱性同様にクリティカルな脆弱性となりえます。

なお、この脆弱性はLXCのようにbind mountを利用可能なLinuxコンテナ全てに影響しますが、OpenVZ以外では第三者に特権プロセスを利用させること(VPSとしての供用)を意図していないため、影響範囲は小さいものと思われます。

対策状況

すでに最初のPoCの公開から約3ヶ月経過しているため、多くのホスティング事業者は対策済みと思われます。

私が利用している海外の事業者では、カーネルのアップデートやライブパッチの適用により、遅くともOpenVZ側の公式アップデート配布直後に対応を済ませていました。

一方で、国内のVPSホスティング事業者でいまだパッチが未適用のところがあるようです。

利用者側に必要な対応

OpenVZとsimfsによるVPSを利用している場合、コンテナ内のファイルに近隣ユーザーがアクセスした可能性があります。

コンテナ内から不正にアクセスされた事を事後にチェックする手段はatimeを監視するくらいしか無いため、対策が済んだ後にシステムをクリーンインストールして秘密鍵等は再生成したほうが良さそうです。 最低限ウイルスチェックなどで不正なプログラムが動いていないかはチェックしたほうがよいでしょう。

Serversman@VPSのネットワーク不調・TCP接続失敗

Serversman@VPSのネットワークが不調でTCP接続確立やコネクション維持に問題が起きている。

サポートに問い合わせても個別事象でしかないそうなので、利用者の方はチェックしてみてほしい。

TCPのSynのドロップ

TCPポートの死活監視をしていると、TCP接続タイムアウトがしばしば発生する。どうやら同一ソースIPアドレスからのTCP synを(おそらくsyn flood対策として)かなり厳しく制限しているようだ。

特に、連続してTCPを張ろうとするとドロップするため低頻度でポーリング監視している普通のモニターツールからは障害が見えにくい。

リモートからApache Benchで小さなファイルにアクセスさせてみたところ、

ab -n 32 -c 4 http://example.com/test.txt

(4並列で32回アクセス) はTCP synの再送中にタイムアウトしてしまった。 念の為 ab -n 32 -c 1 -kに代えてキープアライブ1本なら瞬時に終わる。

Apache Bench中の通信をダンプしてみると、

  • abクライアント側(外部ISP経由)にsyn/ackが返らずsyn再送している。
  • httpdサーバー側(serversman@vps)ではsynの一部が届いておらず、届いている分は正しくackやHTTPレスポンスを返している。

原状synを経路上でsynを数えていて、ある制限を超えると100%落としているように見える。コンテナ内の不具合や、ホストマシンのTCPバッファメモリやコネクション追跡テーブルがあふれているといった個別の事象による問題なら、tcpdumpでsynが見えないのはおかしい。

DDoS対策としてsynフィルタが重要なのはわかるけれど、原状の制限は厳しすぎるように思われる。自分の場合はプライベートRPMリポジトリを置いているため、yumリポジトリDBを取得する一連のlibcurlリクエストが高頻度で失敗するようになっている。

TCP syn flood対策に(動的な)グレーリストで制限をかけるなら、トークンバケツなり乱数なりで確率的に通すよう設定するべきではないだろうか。さもないと、一時的にグレーリストに載ってしまったまともなクライアントはsynを再送しつづけるだけで全く通信できなくなってしまう。

いったいどんなDDoS対策アプライアンスを使っているのかわからないが、DTIのネットワークが定常的に攻撃を受けているとしても、害の方が大きい対策のように思われる。

OpenVZのバッファサイズ制限

TCP不通・切断の件についてサポートに問い合わせると OpenVZによるバッファサイズ制限を超過しているのが理由ではないかと言われた。

Serversman@VPSの提供しているOpenVZ共有カーネルは、実際には仮想マシンではなくLXCと同じLinuxコンテナであり、ゲストカーネルは存在せず個別のVPSのリソースはホストの共有カーネル側が集中管理してカーネルのオーバーヘッドを落としている。

共有カーネル方式の問題として、ファイルディスクリプタやコネクション追跡、各種バッファメモリなど、枯渇しやすいカーネルリソースを一部のコンテナが浪費してしまう危険性があり、OpenVZは独自の方法でコンテナ別制限を加えている。(LXCではcgroupを使っている)

openvzによる制限値は/proc/user_beancountersで見られる。

ServersMan@VPSのEntryプランの制限は次の様になっていた。

# cat /proc/user_beancounters
Version: 2.5
       uid  resource                     held              maxheld              barrier                limit              failcnt
    ?????:  kmemsize                 56793972             91213824  9223372036854775807  9223372036854775807                    0
            lockedpages                     0                    0                  256                  256                    0
            privvmpages                129745               286007  9223372036854775807  9223372036854775807                    0
            shmpages                     3202               134274  9223372036854775807  9223372036854775807                    0
            dummy                           0                    0  9223372036854775807  9223372036854775807                    0
            numproc                        46                  102                  240                  240                    0
            physpages                  214818               262145                    0               262144                    0
            vmguarpages                     0                    0  9223372036854775807  9223372036854775807                    0
            oomguarpages                65858                91180                26112  9223372036854775807                    0
            numtcpsock                     23                  144                  360                  360                    0
            numflock                        1                   13                  188                  206                    0
            numpty                          3                    7                    8                    8                    0
            numsiginfo                      1                   30                  128                  128                    0
            tcpsndbuf                  181072              1731448              1720320              2703360              1779659
            tcprcvbuf                  117200              1728504              1720320              2703360                   91
            othersockbuf                40840              1081208              1126080              2097152                    0
            dgramrcvbuf                     0               261256               262144               262144                   82
            numothersock                   31                  291                  360                  360                    0
            dcachesize               49552266             79544590  9223372036854775807  9223372036854775807                    0
            numfile                       645                 1130                 9312                 9312                    0
            dummy                           0                    0  9223372036854775807  9223372036854775807                    0
            dummy                           0                    0  9223372036854775807  9223372036854775807                    0
            dummy                           0                    0  9223372036854775807  9223372036854775807                    0
            numiptent                      93                   93                  256                  256                    0

個別の意味はOpenVZのwikiをみてもらうとして、このうちtcpsndbuftcprcvbufが1680KBとかなり厳しい。

# sysctl -A | grep mem
...
net.ipv4.tcp_wmem = 4096        16384   4194304
net.ipv4.tcp_rmem = 4096        87380   4194304
...
net.core.wmem_max = 133120
net.core.rmem_max = 133120

TCPのバッファサイズ設定はデフォルトで16KBだが128KBまで増加するようになっていて、最悪14セッションで全て消費してしまう。

送信バッファは書込サイズに応じて自動的に拡大されるので、大きなデータを送信すれば簡単に上限まで消費する。この設定では、普通な使い方をしていてもOpenVZの制限値に一時的に引っかかってしまうのは当たり前だ。 TCPの切断はこれが原因でバッファ割り当てに失敗していた可能性がある。

一方OpenVZのドキュメントによればbarrierを短時間超えるだけなら問題はないとしている。長時間制限を超えていれば新しいコネクションにバッファを割り当てられず接続失敗する、というのはありうるものの、少なくとも自分のコンテナでは超過はごく短時間で、これが原因でTCPが接続確立に失敗するというわけでは無さそうだ。

自分の場合、バッファ制限にかかっていたのはrsync+sshによるバックアップだったので、全てのソケットにSO_SNDBUFSO_RCVBUFをセットしてみた(LD_PRELOADでsocketをフック)ところ、大幅なスループット低下と引き換えにfailcntは増加しなくなった。

が、もちろんTCP接続失敗の問題は解決していない。サポートはOpenVZの制限値をオーバーしているせいだというが、本当かどうかはかなり怪しい。

(ところで、openvzカーネルのバグなのか、net.core.wmem_maxを超えてバッファサイズが増えているようなのだが自分の気のせいだろうか?)

PINGのドロップ

PINGが4%ほどロストしている。 f:id:causeless:20150621202420p:plain

某所からの監視で赤がServersMan@VPSで借りているIPアドレス。 ログを見ると3月ごろから起きていたようだ。だいぶ帯域が逼迫しているようだ。

移行先

料金が倍になってしまうけれど、さくらのVPSあたりか。 国内リージョンのあるLinodeやVulterでもいいけれど、借りようとするたびにストックが無かったりしてあまり試せていない。

IDCFクラウドは最初の1インスタンスは月500円~と安価だけれど、IPv6が使いたいので、Proto41(6in4)を通せないVLAN構成のため移行先選択肢に入らない。

セキュアなメール配送(MTA間通信)を考える+DNSSEC

2015年現在、メール配送の終着点(メールボックスサーバー)とエンドユーザーの間は、ほとんどのメールサービスでPOPS・IMAPSあるいはHTTPS上のウェブメールが利用でき、TLSによるセキュアな通信が可能になっています。

一方で、メール配送、すなわちMTA間のSMTPは未だにセキュアな通信がほとんど実現されていません。 私の確認した限り、実際に著名なサービスでは暗号論的な意味でセキュアなメール配送に対応しているものは見当たりませんでした。

SMTPクライアント側(送信者側のなりすましの排除)

かつて、メールの送り主のメールアドレスは自由に詐称可能でした。 自分のメールアドレスを勝手にスパムに使われて、実情を理解しないスパム被害者からのクレームを受け、対応に苦慮した人も多いと思います。

現在はDKIMを用いた送信者認証が利用でき、受信したサーバーは一部のヘッダの偽装を排除することができるようになりました。

DKIMでは、Fromなど詐称されたくないヘッダについて送信者が署名し、その公開鍵はDNS上で公開されます。

$  dig bulk201106._domainkey.mail.yahoo.co.jp TXT
  ...
bulk201106._domainkey.mail.yahoo.co.jp. 900 IN TXT "v=DKIM1\; g=*\; k=rsa\; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGY5yeT3LUrd1662jmLZE1StxUbNVewEtDBGprWSKoyTdVFxp/OUVmZPom2v7/DCNt6sbisit7SwwpwT9U/gTGFlYHEyh2RShWe05ppMoc3gRBkAlC8SLaZ/SDtVgkUa4eIYkzWt1JXEANOXvXmZ0hxFxhLq0yLio1S7S/kg8KrwIDAQAB"
  ...

このようにDKIMDNSに完全に依存しているため、DNSインジェクションなどには脆弱です。 安全に運用するためには、DNSSECで署名したり、それが不可能であれば攻撃耐性を高めるためにTTLを増やすといった緩和策が必要です。

なお、SPFも送信者をある意味で認証する技術ですが、これはアクセス制限や攻撃検知の一種であり、暗号化ではありません。 SPFでは、送信者のドメイン毎に送信して良いIPアドレス範囲をDNSのTXTレコードで指定します。(IPアドレスベースアクセス制御)

まとめると、送信者側の認証には

  • DKIMによる送信者アドレスの署名
  • DNSSEC署名されたTXTレコードの検証

への対応が必要です。

原状、DKIM署名とDKIM検証、DNSSEC検証に対応したサービスはいくつかあるようですが、 DNSSEC署名されたDKIMレコードを見つけることができませんでした。

SMTPサーバー側(受信者側の認証)

メール本文を盗聴などから保護するためには、SMTPサーバーとセキュアに通信する必要がありますが、こちらはやや困難です。

TLSによる暗号化通信

SMTPサーバーとの接続ではSTARTTLSコマンドを送るとTLSによる暗号化を利用できます。(あるいは implicitな SMTP over SSLを使う)

通常、SMTPサーバーはそのホスト名に対応する証明書チェーンを提示します。クライアントはその証明書を、手元の信頼された認証局ルート証明書の公開鍵で連鎖的に信頼されているか確認し、証明書の識別名(あるいはSANs)がサーバーのホスト名と一致することをもって信頼されたサーバーであることを確認します。

このSMTPS (SMTP over SSL あるいは with STARTTLS)は、著名なサービスの多くで有効になっているため、SMTPサーバーどうしの通信は暗号化されている事が多くなっています。

MXレコードの署名確認

だれだか分からない匿名の相手と暗号化通信しても安全にはなりません。相手が本当にメールを受け取るべき相手なのか認証する必要があります。

MTAではメールアドレスのドメイン部分のMXレコードをDNSで問い合わせることで、正しい信頼された送信先サーバーを特定します。

$ dig gmail.com MX
...
gmail.com.      2628    IN      MX      5 gmail-smtp-in.l.google.com.

このように、メール配送先決定がDNSに依存しているため、この結果が正しいことを保証するためには、MXレコードがDNSSECによって署名されている必要があります。さもないと、DNSインジェクションによって攻撃者が所有しているドメインへ接続させられているかもしれません。

残念ながら、著名なサービスでMXレコードがDNSSEC署名されているものは見当たりませんでした。

フォールバックの無効化

SMTPにはフォールバック問題があります。 これはもしSTARTTLSコマンドや検証に失敗すると、SMTPクライアントはサーバーがTLSに対応していないと判断して、TLSを使わず平文のまま通信してしまうというものです。

ウェブの世界では、アドレスにhttps://という新しいスキームが導入されたため、このような問題はありません。https://と指定したのにHTTP通信している場合、それは実装の脆弱性です。 さらにHSTSの導入により、InternetExpolorerを除くモダンブラウザでは、ユーザーの入力ミスやウェブサイトの誤動作で誤ってhttp://へ接続してしまうことを、『最初のアクセス』を除いて、防ぐことができるようになりました。 Google Chromeでは、『最初のアクセス』を保護するためにHSTS preloading(ブラウザ出荷時にHSTSが有効と知られているドメインのリストを同梱しておく)を行っています。さらに将来的には後述するDANEの導入により、全てのアクセスが保護されるものと期待できます。

TLSSSL自体やその実装にも、いくどかフォールバック・ダウングレード脆弱性が発覚していますが、FALLBACK_SCSVオプションやSSLv3.0の無効化など、脆弱性対応がなされています。

メールアドレスにはTLSSSL)導入に際して、https://に相当する新しいスキームが取り入れられることはありませんでした。 そのため、接続先サーバーがSMTPに対応しているのか調べる方法は、ほとんどありません。せいぜい、著名なサービスに対してフォールバックしない、というホワイトリストを作成しておくくらいです。

将来的には、DANEによる証明書存在証明をSMTPフォールバック問題の解決に利用することが期待されます。これはRFCドラフトになっています。

DANEでは、TLSAレコードという新しいレコードにより、ホスト名・プロトコル・ポート番号に対して、信頼済み証明書あるいはそのハッシュを明示することができます。 TLSAレコードが存在することをDNSSECで検証することが出来れば、そのSMTPSサーバーではTLS接続が可能であることみなせるため、フォールバック脆弱性は解決されます。

まとめると、

  • サーバーのホスト名に対して有効な証明書
  • DNSSEC署名されたMXレコードによる信頼されたメールホスト名
  • DNSSEC署名されたTLSAレコードによるフォールバックの禁止

これらに対応する必要があります。

MTA実装ではすでにDANE対応が進められていますが、DNSSEC署名されたTLSAレコードを公開しているサービスはみかけません。

まとめ

原状、暗号を用いたセキュアなメール配送を行っているメールサーバーは、DNSSEC署名の対応状況から見て非常に少ないと考えられます。

DNSやメールサーバー間の通信経路は、多くのレイヤのネットワークオペレータの努力により、十分には安全であると考えられていますが、完全ではありません。(もちろん暗号を利用すれば完全というわけではありませんが)

メールを預けるメールボックスサービスがいくら信頼できるとしても、MTA間の通信が安全であるかといえば、私には不十分と感じられます。

重要なメールを送信する際は、S/MIMEPGP公開鍵暗号による、エンドツーエンドの暗号化の導入を検討してみてください。学習コストは高いですが、導入自体は(少なくともDNSSECの煩雑さよりは)容易です。

(6/26 typo・微妙な表現を修正)

IDCFクラウドを触ってみたメモ

IDCFクラウド http://www.idcf.jp/cloud/ という新し目?のサービスを少し触ってみた。

仮想化

VMware+独自のコンソールとAPI

ネットワーク周り

仮想マシンは全てプライベートIPなVLANに接続されている。 IPアドレス範囲は10.0.0.0/8から/21が割り当てられ変更できないので、既に使っている場合はコンフリクトに注意。

IPv6は非対応で、ドキュメントやFAQで触れてないあたり、将来対応する予定も無いようだ。

内向き接続(DNAT)

初期割り当てのIPアドレスポートフォワード。 追加購入したIPアドレスでは加えてスタティックNATを設定できる。

ポートフォワードと呼ばれているDNAPT機能では、NAT可能なのはTCPUDPのみ。つまりICMPなど他のL4プロトコルは通せない。

追加購入したIPアドレスのみ、スタティックNATという名前でL3アドレス変換のみを行うDNATに設定できるようだ。

デフォルトでファイアーウォールが有効で全拒否設定になっているので、pingも帰ってこない。TCPUDP・ICMPポートとIPアドレス範囲で設定ができる。他のL4プロトコルは通せない。

キャリアグレードNATが幅広く導入されてしまった原状、通常のクライアント相手のサービスはTCPUDPしか選択肢が無いのでまあ十分か。

外向き接続(SNAT)

外向き接続は全て初期割り当てのIPアドレス一つからSNAPTされて出ることになる。追加IPアドレスからは外向き接続はできないようなので、メールサーバAPI制限などを理由にソースIPアドレスを分散したいような用向きには使えない。 追加IPアドレススタティックNATで固定割り当てすると、SNAT時に追加IPアドレスが優先されるとのこと。(http://www.idcf.jp/cloud/faq/nw_007.html

外向きSNAPTはTCPUDP・ICMPは通してくれるようだけれど、他のL4プロトコルを使う外部サービスが利用できない。VPNもover UDP/TCPのものを選択せざるをえない。

He.netのようなProto-41 IPv6トンネルを使えないため、IPv6対応するのが難しくなるのが個人的にはマイナス。

ぱっと見た限りSNATポート範囲は32768以降のようだ。大量のセッションを張ったらどうなるか、あるいは利用規約上大丈夫なのかは不明。要検証。

ネットワークベンチ結果はアカウント作成直後なので制限されているのかもしれない。

$ speedtest-cli
Retrieving speedtest.net configuration...
Retrieving speedtest.net server list...
Testing from SOFTBANK IDC Corp. (210.140.77.228)...
Selecting best server based on latency...
Hosted by Telin (Tokyo) [6.97 km]: 6.883 ms
Testing download speed........................................
Download: 389.43 Mbit/s
Testing upload speed..................................................
Upload: 25.99 Mbit/s

ロードバランサ

仮想ルータのDNATがロードバランサを兼ねているようでTCP/HTTPを振り分け可能。

外部からのアクセスをファイアーウォール機能で制限すれば、VLAN内からでもRDBサーバなどの分散にも使える模様。

ストレージ

ルートパーティションは15GB固定の仮想ボリュームとしてOSテンプレートが用意されている。追加ストレージは仮想ボリュームを作成して追加マウントする。

ddで100~200MB/s程度なので、ストレージはSSDではなさそうだけれど十分。

CentOSイメージを見たところ、VMwareツールが入ってたりデフォルトでpostfixが立ち上がってたり、ログインバナーが入ってたりするくらいか。

IPv6は無効化されていなかったので、意図しないLAN内からのアクセス制限回避に注意。

ISO持ち込みは可能だが、FAQを見る限りserverライセンスでないwindowsはアクセス制限していても禁止の模様。艦これ勢は涙目?

スナップショットとHA

実行中にスナップショットが使えて仮想マシンの複製もできるようだ。

HW障害時は自動フェイルオーバーしてくれるようだが、障害時にどこまで同期されているのか記載が無い。

VMwareのHAそのままならクラッシュ時点のディスクイメージからそのまま再起動と思われる。再起動時にfsckやDBのリプレイが走るだろうから数分くらいはかかりそう。

作成自動化

仮想マシン作成時にSSHキーを追加してくれる機能くらい。 あとはスナップショットからコピーするくらいか。

ただしマシン作成時にSSHキーを設定してもrootパスワード生成は普通に生成され平文メールで送られてくるし、sshd_configが書き換わっているわけでは無さそうなので無効化は必須。

UIコンソール

二段階認証あり。ログイン通知はないけれどアカウントの設定変更・マシン作成通知などはメールで来る。

細かい操作一つ一つにモーダルダイアログ、確認ボタンが表示されるあたり、利便性や可視性より誤操作を防ぎたい感じをうける。

全体をみて

基本的に単一VLAN内の数個のインスタンスで完結する標準的なサービス(ウェブアプリとか)を、簡単に構築したい向け。

最小構成は個人向けも意識した価格設定だけど、VPNIPv6通したいとかネットワークを細かく弄りたい人は普通にVPS使った方が良さそう。ほかのクラウドもそういうところあるけど。

(修正 2015/06/29 ソースIPアドレスは変更可能な旨、IDCFのサポートアカウントから指摘いただいたので訂正)

fluent-plugin-secure-forwardの脆弱性と匿名TLSの話

先日fluentdのサードパーティプラグインfluent-plugin-secure-forward言及した件について、更新v0.3.2が公開されています。

何かの間違いでfluent-plugin-secure-forwardを実運用に供してしまった方は、なるべく早く更新することをお勧めします。

脆弱性の内容と経緯

以下は、v0.3.2時点では修正済みとされている、v0.2.6*1当時、私が把握していた脆弱性について記載しています。

TLSの証明書検証不備

README.mdでの記述に反して、実際のコードはTLSクライアント側にリモート証明書検証コードが実装されていませんでした。

TLSに対するよく知られた攻撃手法として、証明書検証の不備を突いた中間者攻撃( 中間者攻撃 - Wikipedia) がありますが、このプラグインはそれを許容する前提で設計され、事実上TLSを認証のない匿名TLSとしてのみ利用していました。

このプラグインには匿名TLSの上に独自の認証機構をもち、shared_keyと呼ばれる共有鍵のハッシュを用いてクライントとサーバーを相互に認証することになっていましたが、ログ(通信するメッセージ)は全く暗号化されていませんでした。

前述のようにTLSの中間者攻撃が可能なため、shared_keyを鍵にしてログを(TLSの上でさらに)暗号化しないと、中間者はログを取得・改竄することができてしまします。

独自の認証手法の問題

独自に実装された認証手法自体にも問題がありました。

認証の中で、クライアントはサーバーへshared_key_saltと呼ばれているnonceを送りますが、このnonceは双方向の認証で使い回されておりnonceとして機能しておらず、かつ認証レスポンスを正しく検証していなかったため、偽のサーバーは正規のサーバー無しに(中間者攻撃なしに)クライアントからログを収集できました。.

また、サーバーはデフォルトではクライアントへnonceを送らないため、クライアントの認証はリプレイ攻撃が可能でした。 これは過去に一度中間者攻撃に成功した攻撃者に、サーバーへの恒久的なアクセス、偽造ログの挿入を許してしまいます。

セキュリティ設計の問題

平文のTCP上でTLSが実現しているように、極論すれば匿名のTLS上でセキュアな通信は不可能ではありません。

実際、このプラグインは匿名のTLS上で、TLSに頼らないセキュアな通信プロトコルを実現しようとしていたものと思われます。

しかし、事前共有鍵一つでセキュアな通信を実現するには安全な事前共有鍵認証と安全な鍵共有を独自実装し、メッセージを暗号化することが必須で、その実装は簡単とは言えません。

一方で、これらは既にTLSにPre-Shared-Key方式(TLS_PSK_*)として定義され実装済みのものです。

$ openssl ciphers -v aPSK
PSK-AES256-CBC-SHA      SSLv3 Kx=PSK      Au=PSK  Enc=AES(256)  Mac=SHA1
PSK-AES128-CBC-SHA      SSLv3 Kx=PSK      Au=PSK  Enc=AES(128)  Mac=SHA1
PSK-3DES-EDE-CBC-SHA    SSLv3 Kx=PSK      Au=PSK  Enc=3DES(168) Mac=SHA1
PSK-RC4-SHA             SSLv3 Kx=PSK      Au=PSK  Enc=RC4(128)  Mac=SHA1

報告と改修の経緯

まず、私はコードにコミットしておらず、改修は全て開発者の@tagomris氏(と助言されたおそらくTreasure dataの中の人)の尽力による功績です。

v0.2.6当時、最初私は中間者攻撃可能な証明書検証不備について、開発者の方に問い合わせましたが、証明書の有効性を検証をしていない実装は意図的なものであり、変更は難しい、とのことでした。

セキュリティに対して前提条件や要求される安全性には様々なバリエーションがあります。しかし、中間者攻撃に対して漏洩・改竄を許容するメンテナのセキュリティポリシーは、残念ながら私の必要とは合致せず、修正コードをコミットすることもありませんでした。

このプラグインはAPLでライセンスされており、ユーザーは無償で利用できる一方で開発者に文句を言う筋合いはありません。またコミットもせず横槍を入れるような指摘も失礼だったかと思います。

しかし、このプラグインはfluentdの公式サイトから、半ば推奨される形でリンクされてしまっていたため、いくらか実運用した利用者がいたものと思われます。(公開以前は100kダウンロード程度でした) fluentdはログ配送サーバーであり、万が一secure_forwardが利用されていたら、無関係のユーザーの情報が危険に晒される事になります。そのため、このエントリの公開を準備していました。

その後、v0.3.0でクライアント側にリモート証明書の検証機構が追加され、v0.3.2でリプレイ対策のサーバーnonceが導入されたようです。 少なくとも開発者の周囲の方、おそらくTreasure Data社の中の方に、証明書検証不備が脆弱性であると認識している方がいる、という点は安心材料かもしれません。

これでひとまず、『使うな』から『注意して使う』にできるものと思います。 

さいごに

TLSのリモート証明書検証不備・匿名TLSの利用は、少なくともWebの領域では明白かつ重大な脆弱性と見なされています。

一方で、独自プロトコルTLSで保護しているように見せながら、実際には適切な証明書検証をしていないアプリケーションが数多く存在するというレポートもあります。

TLSは(ニア)ゼロコンフィグレーションで安全性を確保してくれる魔法ではありません。 TLSを安全に使うには最低限、

  • 公開鍵基盤発行の証明書と有効範囲(ドメイン名)検査
  • プライベート認証局による証明書の自己管理

が必要です。このエントリが、TLSの誤用を少しでも減らすことになればと願います。

証明書作成サイトCertCraft.netを公開しました

CertCraft.netというテスト用x509証明書(いわゆるSSL証明書)と認証局(Certificate Authority)をオンラインで作成するツールを公開しました。

f:id:causeless:20150524174442p:plain

なにこれ?

これはTLS Transport Layer Security (旧称SSL)で使われる証明書、いわゆるオレオレ証明書・オレオレ認証局と呼ばれる、自己署名証明書認証局のモックを作るウェブアプリです。

  • 証明書失効のテストをしたいけど買うのは高いし時間がかかる
  • 設定ファイルの記述がきちんと動作するか確かめたい
  • 鍵管理が面倒ですべて同じ鍵で動作テストしてしまう

そんなかたにお勧めします。

ソフト開発

TLS接続を扱うアプリケーション開発やそのテストを書いている時に、妥当なサーバー・クライアント証明書や、失効された証明書が必要なことは多いと思います。

自己署名証明書を作るのは比較的簡単ですが、実際に利用されている証明書と同じ機能を試そうとすると厄介になります。

例えば、モダンブラウザが受け入れるサーバー証明書の要件を満たすには、最低限次のものを用意する必要があります。

  • 妥当な制約を持つルートCA証明書
  • 妥当な制約を持つサーバー証明書
  • 機能するOCSPレスポンダ(署名サーバー)

また失効をテストする際にはOCSPの応答を変更する必要があり、証明書失効リストCRLの生成も必要です。

certcraft.netではCA証明書を作成すると同時に秘密鍵、CRLとOCSPレスポンダが作成され、 OCSPチェックが必須に設定されているブラウザにも通用する証明書が作成できます。

設定・運用テストや例示用

実際にSSL証明書を購入する前に、設定例を試したりするためにも有効な証明書は必要です。

生成されるCAでは何時でも証明書を失効させる事が出来るため、万一の漏洩時に失効させる手順の確認や、CRLやOCSPからの失効情報がクライアントに正しく反映されるかどうか動作テストしておくこともできます。

実際に動く設定例を書く際など、使い捨ての証明書の作成にも便利かもしれません。

PKIセキュリティの学習用

certcraft.netではほとんどの証明書を作成することができます。これはある意味で非常に危険なことに思われるかもしれませんが、実際に悪用することは出来ません。

たとえばcertcraft.netでは誰でもhttps://www.google.com用に署名されたサーバー証明書を作成する事ができます。しかし、生成した証明書を利用するには、作成元になったルートCA証明書を信頼済み証明書としてブラウザに追加する必要があります。 新しくつくったルートCA証明書は誰も信頼していないため、certcraft.netで作ったサーバー証明書は基本的に無害です。

TLSが依存している公開鍵基盤は、信頼済みルート証明書のうち、一番安全な認証局ではなく、もっとも安全でない認証局によって安全性が制限される(ドベネックの樽という事実を理解するためにも、セキュアでないルートCAがどういったものか試してみていただければと思います

注意

certcraft.netはあくまでテスト用の証明書を作成するサービスです。

生成した証明書は実際にセキュアな通信を確立するために使うことは出来ません。生成された証明書や秘密鍵安全性はまったくありません。保証されていません、ではなくありません秘密鍵は(CAの機能を実現するため)サーバ側に保管されています。

無償で第三者に対して通用するSSL証明書が必要なかたは、let's encryptなどを参照してください。 無償でセキュアな認証局を構築したい方は、opensslなどのツールをよく検証した上でお使いください。

さいごに

TLSをバックエンドとして使っていながら、証明書検証が正しく実装されておらずセキュアでないソフトウェアを排除するためにも、一人でも多くの人に実際に証明書と認証局を触ってみて欲しい。そんな考えで書いてみたアプリです。

Ruby on Railsの学習も兼ねているため見かけはかなり大雑把ですが、一通り機能するCAを簡単に作成出来ます。 証明書検証エラーを起こしてみたことが無い方はぜひ試してみてください。

不具合・ご意見・ご要望は twitterかメールへお寄せください。