Nginxのtoken binding実装を試す

20181016追記
Remove support for Token Binding
https://chromium.googlesource.com/chromium/src/+/2243e8002e3025b3f8386c13be7437fc8b597e2a

chromeの実装は削除されました下記も合わせて参照のこと
https://groups.google.com/a/chromium.org/forum/?nomobile=true#!topic/blink-dev/OkdLUyYmY1E

Token Binding

CookieやOAuth2.0のトークンはbearer tokenと呼ばれ、そのトークンを持ってる人であれば使用することができます。Token Bindingは、各TLSコネクション固有の秘密鍵で署名することで、第三者による別のコネクションにおけるそれらの使用を制限できるものです。


仕様及びユースケースについては、IETFで仕様策定中のドキュメントを参照ください

ngx_token_binding

GoogleがNginxのtoken bindingモジュールを公開していたので試す
https://github.com/google/ngx_token_binding

ビルド
# ngx_token_binding 準備
git clone https://github.com/google/ngx_token_binding.git
cd ./ngx_token_binding 
git submodule update --init
cd ../

# openssl に変更を加える
git clone https://github.com/openssl/openssl.git
vim ./openssl/ssl/t1_lib.c

git diff
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index ce728b0..e2e01bb 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -2461,7 +2461,7 @@ static int ssl_scan_clienthello_tlsext(SSL *s, CLIENTHELLO_MSG *hello, int *al)
          * callback and record the extension number so that an appropriate
          * ServerHello may be later returned.
          */
-        else if (!s->hit) {
+        else {
             if (custom_ext_parse(s, 1, currext->type,
                     PACKET_data(&currext->data),
                     PACKET_remaining(&currext->data), al) <= 0)

#ビルド
wget https://nginx.org/download/nginx-1.11.6.tar.gz
tar zxvf nginx-1.11.6.tar.gz
cd ./nginx-1.11.6/
./configure --with-http_ssl_module --with-openssl=/home/vagrant/openssl/ --addmodule=/home/vagrant/ngx_token_binding

make
sudo make install


nginx.conf

    server {
        listen       80;
        server_name  localhost;
        add_header set-cookie id=test;
    }
    server {
        listen       443 ssl ;
        server_name  localhost;

        token_binding on;
        token_binding_cookie all;
        token_binding_secret secret;

        ssl_certificate /home/vagrant/server.crt;
        ssl_certificate_key /home/vagrant/server.key;

        location / {
            proxy_pass http://127.0.0.1:80;
        }
    }

試す

ChromeのCanary buildがToken Bindingに対応しているので (chrome://flags/ から Token Bindingを有効にする)、それでNginxに接続する。
今回は単純に、ブラウザと単一のサーバとの通信を行い、CookieにToken Bindingのパラメータがつくことを確認する(ユースケースのFirst-party Use Cases)


ブラウザからサーバに複数回アクセスし、デベロッパーツールを確認する

レスポンスのset-cookie及び、リクエストのcookieに文字列が付与されていることが確認できる。


あわせて、サーバ側のログでは 「id=test」のcookieとして認識されていることも確認できる

tail /var/log/nginx/nginx-https.log
192.168.0.1 - - [24/Nov/2016:16:09:17 +0000] "GET / HTTP/1.1" 200 cookie: id=test 


流れにするとこんな感じ


このCookieはもちろん他のブラウザにセットしても正しく使用することはできない

NTP leap indicatorを上書きするProxyを書いた

前置き


かなり無理矢理かつ、動作を保証するものではありません。
手習いで試しに書いてみたぐらいの温度感です


うるう秒

元旦「うるう秒」でエンジニア悲鳴 「年末年始がなくなる」”と言ったニュースサイトでも取り上げられているように、1月1日 日本時間朝9時にうるう秒が挿入されます。つまり、8時59分60秒 という時間が挿入されます。


60秒が挿入されるとはどういうことか
NTP うるう秒(閏秒)」こちらのサイトの情報を引用させていただくと

日付(JST) NTP time LI
2006/01/01 08:59:59 3345062399 01
2006/01/01 08:59:60 3345062400 01
2006/01/01 09:00:00 3345062400 00

NTPサーバからのレスポンスには事前にうるう秒の挿入を示すLeap Indicatorというフラグがセットされます。そうして、60秒から00秒にかけては同じ時間がレスポンスされて帰ってきます。


このLIですが、このLIがセットされているとSlewモードからStepモードに移行してしまうNTPクライアントのバグなども存在していますし、なかなか厄介な存在です。


前回は、このLIがNTPクライアントに到達しないように、該当時間だけ上位のNTPサーバとの同期を停止するという方法を取ったという人もいるようです。もちろん、60秒を挿入しないぶん1秒早く時刻が刻まれていきますが、殆どのソフトウェアは個々に対応が進んでいるとはいえ予期せぬうるう秒に起因する不具合を踏むよりはマシなのかと思います。

NTP leap indicatorを上書きするProxyを書いた

上位のNTPサーバと同期を止める方法は、ローカルのハードウェアクロックを頼りにしており、それはそれで心もとないなと思い、LIフラグをオフにするUDP Proxyを書いてみました。


unset-leap-indicator-proxy
https://github.com/flano-yuki/unset-leap-indicator-proxy


NTPサーバを指定して起動すると、UDPの123 portでLISTENし、受け取ったNTP Requestを指定したサーバにProxyします。

vagrant@vagrant:~/tmp$ sudo go run ./proxy.go -v ntp.nict.jp
Info: Start Proxy on  :123  to ntp.nict.jp
Received from 192.168.0.179:123
Received from 192.168.0.179:123
Received from 192.168.0.179:123



このProxyはNTPパケットを実際にパースすることはなく、単純にUDPボディの上位2bit(Leap Indicator)を 00 に上書きします。
これによってNTPクライアントにLIが入ることもありません。しかし、うるう秒のタイミングでは60秒が挿入されず1秒ずれてしまうので、そこからはNTPの時刻同期の仕組みによってずれが解消されていくものと思います。


ACL機能がないため踏み台攻撃に注意してください。

CT対応を示すExpect-CTヘッダとは

追記 2021/11/15
Expect-CTヘッダの役割は、CTの利用が必須となり不要となる方向です
asnokaze.hatenablog.com


Certificate Transparency

Certificate Transparencyと呼ばれる、不正な証明書の発行を検知する仕組みがGoogle社によって考案され、RFC 6962として標準化されています。もちろん、Google Chromeもこの機能に対応しており、ログサーバーにSigned Certificate Timestamp(SCT)を検証し、正しく発行された証明書なのかを検証します。


詳しい仕組みについては、各社CAの説明を読むとわかりやすいかと思います。

このCTですが、GoogleChrome teamはCA/Browserにて、2017年10月以降に発行された証明書は信頼されるためにChromeのCTポリシーを尊守することが期待される、とアナウンスをしているようです。
https://groups.google.com/a/chromium.org/forum/#!msg/ct-policy/78N3SMcqUGw/ykIwHXuqAQAJ


ChromeのCTポリシーは、chromiumwikiより確認できます。
https://www.chromium.org/Home/chromium-security/certificate-transparency


また、来月行われるIETF97のHTTPbis wgで、サーバが明示的にCTに対応している旨をブラウザに通知するExpect-CTヘッダの議論が行われる予定です。これにより、何かの不具合が生じたときに検知できるようになります。

Expect-CT ヘッダ

まだ、議論が開始するような段階のためまだまだ決定したものではありませんが、Expect-CT ヘッダは以下のようなもののようです。(今のところ著者の個人githubリポジトリより仕様が確認できます。)



CTに対応してるWebサーバは、httpsで接続を受けた際、レスポンスヘッダにExpect-CTヘッダを付加します。

expect-ct: enforce;max-age=3600;report-uri=https://example.com/report-uri


これを受け取ったユーザエージェントは、それ以降の接続でCT対応のサーバ証明書が得られなかった場合は何かがおかしいと判断します。その際、Expect-CTヘッダで指定されたreport-uriにレポートを送信します。


Expect-CTヘッダには、以下のようなディレクティブが指定できます

  • enforce: CTポリシーに違反した際、接続を拒否するように指示する
  • max-age: このExpect-CTヘッダの有効期間を秒で指定
  • report-uri: CTポリシーに違反したときのレポート送信先絶対URL


レポートされる内容は、jsonで以下の通りです。

{
  "date-time": date-time,
  "hostname": hostname,
  "port": port,
  "effective-expiration-date": expiration-date,
  "served-certificate-chain": [ (MUST be in the order served)
    pem1, ... pemN
  ],
  "validated-certificate-chain":
    pem1, ... pemN
  ],
  "scts": [
    sct1, ... sctN
  ]
}

時刻と、サーバの情報、及び証明書チェーンとsctが送られるようです。

CloudFlareの提案するHTTP/2の圧縮辞書拡張

IETFのHTTPbis wgでCloudFlareの方より「Compression Dictionaries for HTTP/2」(URL)という仕様が提案されている。


これは、Content-Encodingヘッダで指定される圧縮アルゴリズム向けの初期ウィンドウに使用される辞書データを事前に送る拡張を定義する。


CloudFlareではすでに検証されており、通常のDeflate(zlib compression level 8))より最大1.50倍、平均で1.10倍縮小できたとしている。

背景

  • CSS/JS/HTMLといったアセットは、Content-Encodingを使用しDeflateやBrotliで圧縮される
  • HTTP/2以前は、リクエスト数を少なくするためにインライン化などを行っていた
  • 圧縮アルゴリズムは、後方一致するほど圧縮率が上がる
  • HTTP/2で小さいファイルを多重化して送ることが推奨化されており、圧縮率が上がらない。

拡張フレーム

この仕様では、2つの拡張フレームを定義している。大雑把に言うと辞書を宣言するフレームと、辞書を適応するフレームである。

SET_DICTIONARYフレーム
   +-------------+-------------+
   | Dict ID (8) |   Size (8)  |
   +-------------+-------------+
  • Dict ID: 辞書のためのスロットを指定する値
  • Size: 辞書のサイズ
USE_DICTIONARY
   +-------------+
   | Dict ID (8) |
   +-------------+

Dict ID: SET_DICTIONARYでセットされた辞書のスロットを指定する

動作

サーバ側

サーバは、最初のDATAフレームを送る前にいかなるストリーム上でSET_DICTIONARYフレームを送信できます。サーバは最初の2^Sizeの圧縮されてないオクテットを後続のストリームのためのCompression Dictionaryとして使用します。


サーバはストリームでDATAフレームを送信する前にUSE_DICTIONARYフレームを送ることで、Compression Dictionaryでストリームを圧縮できます。

クライアント側

クライアントはSET_DICTIONARYフレームを受信するとそのサイズ分だけ辞書のためにスロットを予約します。
そのストリームでDATAを受信したあと、最初の2^Sizeオクテットを辞書として保存します。


USE_DICTIONARYを受け取った際、クライアントはDATAの複合に指定された辞書を使用します。

googleの新しい時刻同期プロトコル Roughtimeとは

[追記] 2020年1月時点の動向について、新しく記事を書きました
asnokaze.hatenablog.com


Googleの「Adam Langley氏のブログ」で、新しい時刻同期プロトコルについて紹介されている。このRoughtimeは特定のタイムサーバに依存しないセキュアな方法で時刻同期を行うことを目的としたプロトコルです。すでに、googleのサーバで動作しており、roughtime.sandbox.google.com:2002に向けて公開されている専用クライアントで接続できる。


現在のセキュリティは現在の時刻に依存しており、その重要性はましています。証明書の有効期限や、OCSPレスポンス、Kerberosのチケット、DNSSECの応用やPGP鍵といった機能でも重要です。しかし、Chromeの証明書エラーのうち25%はローカルの時刻に起因するとしているとしています。


現在、最も使用されているNTPプロトコルの認証機能はほとんど使用されていないとし、MITM攻撃で時刻を操作することが出きる旨記述している。


この問題に対して、それらを改善することを目的に新しい時刻同期プロトコルroughtimeを提案している。

roughtimeの特徴

  • UDPプロトコル
    • 増幅攻撃を防ぐため、リクエストメッセージは1kバイトになるようにパディングされる
  • 現段階では正確な時刻同期を目標としておらず、10秒以下であれば満足とする
  • 認証されているタイムサーバでも複数のタイムサーバを用いて正しくない振る舞いをするタイムサーバを検出する
  • 公開鍵方式で署名される
    • Ed25519のみをサポートしてる
    • サーバのレスポンスは、クライアントが生成して提出したノンスと時刻について署名して応答される
    • long-term identity key から委譲されるonline public keyを使用する
    • 複数のリクエストに対してバッチ的に署名する (Merkle treeのルートに対して署名する)
    • レスポンスに含まれるMerkle treeは、実際には対象のノンスから、ルーに到達するのに必要なノードとindex(left or rigth)が与えられる(木のリーフは1~64)
    • Skylakeチップで1コアで1秒あたり430万リクエストを署名できると見積もっている
  • うるう秒を24時間に分割して時刻に反映する

roughtimeプロトコル概要

クライアントは乱数を生成し、それをnonceとしてRoughtime リクエストをサーバに送信します(1024バイトまでパディングされる)。サーバはそのノンスと時刻に対する署名、サーバ証明書などをroughtimeレスポンスとして返します。


この時、クライアントが生成したノンスに対して署名されて返ってくるため、その時生成されたことが保証されます。


つまり、サーバAに問い合わせて結果を得たあとに,サーバBに問い合わせた場合。サーバAから得られた時刻 より、サーバBから得られた時刻のほうが新しいはずということがわかります。この時サーバBに問い合わせるときに、サーバAからの応答をハッシュしてノンスとして使えば、その順序も確実であるといえます。それぞれから得られた時刻で矛盾があればなにかおかしいことに気づくことができます。


この手続を6つ以上の独立したサーバで行えば不正なサーバを検出でき、正しい時刻を定めるのに十分正確だとしています。

roughtimeメッセージ

Roughtime メッセージ(リクエスト or レスポンス)は、タグの数を示すヘッダと、複数のタグと値のペアからなります。タグ名は32bit値であり、ドキュメント中では文字として"NONC "や"CERT"と記述されます。3文字の場合は4文字目だけ16進数で明示的に記述されます("SIG\x00")。タグはそれぞれ値をもちます。

roughtime リクエス

Roughtime リクエストは、NONCタグを持つメッセージです。NONC の値は64バイト値です。それ以外のタグは無視されます。増幅攻撃を防ぐためにPAD\xffタグを用いて1024バイト以上にする必要があります。

Roughtimeレスポンス

レスポンスは以下のタグで構成されます

  • SREP: 署名されるメッセージ。Merkle treeの根と、時刻を示す2つのタグMIDP,RADIを含みます
  • SIG\x00: SREPの署名値である64バイト値。Ed25519 のみが署名アルゴリズムとしてサポートされており。
  • CERT:サーバの証明書。署名に使用するonline public keyと有効期間と、この鍵に対する署名。
  • INDXPATH: 複数のクライアントのリクエストに含まれるノンスはMerkle treeの葉となる。複数のリクエストに対して署名を行うため、Merkle treeのルートのみに署名を行う。このMerkle treeをルートまでたどるのに必要な経路PATH(ノードのリスト)と、たどる際のノードが左右のどちらのノードなのかを示すINDX
Roughtime UTC

タイムスタンプはMIDPRADIの2つのタグで表現されます。MIDPタグはマイクロ秒で時刻範囲の中心点をunit64として持ちます。一方RADI タグは時刻範囲の半径をマイクロ秒でuint32で表現します。


Roughtime の"正しい時刻"は、24時間でうるう秒をならしたUTCです。1日かけて均等に反映されていきます。

実装

C++ と Goの実装が公開されている。 C++はbazelを導入し、ビルドする必要があるが、Goは比較的すぐに動作確認することができる。

vagrant@vagrant:~/go/gopath/src$ git clone https://roughtime.googlesource.com/roughtime roughtime.googlesource.com

vagrant@vagrant:~/go/gopath/src/roughtime.googlesource.com/go/client$ go build

vagrant@vagrant:~/go/gopath/src/roughtime.googlesource.com/go/client$ ./client --servers-file=../../roughtime-servers.json --chain-file=$HOME/roughtime-chain.json
Quorum set to 1 servers because not enough valid servers were found to meet the default (3)!
real-time delta: -16.306032ms


roughtime-servers.jsonGoogleのサーバとその公開鍵が指定されており時刻の誤差が表示される、一つしかないのでサーバの検証は実施されない。


20/10/11 表現を少々修正しました
前) 正確な時刻同期を目標としておらず、10秒程度の誤差は許容する
後) 現段階では正確な時刻同期を目標としておらず、10秒以下であれば満足とする

XSSを防ぐ新しいXSS-Protectionヘッダ

evalとreportOnlyについて追記しました (2016/10/10)


2016/10/20
仕様名は以下の通りになりました。
Anti-XSS Response-Time Uniqueness Requirement


また、ヘッダ名は、XSS-Protectionヘッダではなく、ARTURヘッダとなっておりますが、また変更される可能性があります。


Googleの調査によると、CSPによるXSSの防止は現実的にデプロイの欠陥によりXSSの防止効果がないことを示しています。調査は「CSP Is Dead, Long Live CSP!」としてACMのカンファレンスで発表され、ペーパーも閲覧することができます。


9月に行われたW3C TPAC 2016のWebAppSecのミーティングで議論され、GoogleのMike West氏より新しくXSS Protectionという仕様が提案されている。仕様は氏のリポジトリから確認することができる。
https://mikewest.github.io/artur-yes/


このXSS Protectionは、新しくXSS-Protectionヘッダを定義し、CSPの一部の機能を引き取りX-XSS-Protectionでサポートされているような機能を有します。目的は簡潔でわかりやすく、CSPと同等のXSS軽減機能を提供することです

XSS-Protectionヘッダ

XSS-Protectionヘッダは、json形式のヘッダ値を持ちます。
これについては、現在IETFで議論されている「A JSON Encoding for HTTP Header Field Values」という仕様を使用しています。

下記の例では、XSS-Protectionヘッダでノンス値を指定します。その場合、そのノンス値が指定されたスクリプトタグのみが実行されます。

XSS-Protection: { "nonce": "abcdefg" }
<!-- このスクリプトは実行される -->
<script src="script.js" nonce="abcdefg"></script>

<!-- このスクリプトは実行されません -->
<script src="script.js"></script>

指定できる項目

eval

XSS-Protectionでは、デフォルトではevalはブロックされますがevalメンバーにunsafe-allowを指定することで、hashなどの条件が一致する場合は実行できるようになります

XSS-Protection: { "hash": [ "sha256-abcd...", "sha256-zyx...", "eval": "unsafe-allow" ] }
hash

実行を許可するスクリプトハッシュ値を指定する

XSS-Protection: { "hash": [ "sha256-abcd...", "sha256-zyx..." ] }
<script src="script.js" integrity="sha256-abcd..."></script>
<script>
  // Content which hashes to abcd... goes here.
</script>
nonce

実行を許可するScriptに指定されるノンス属性の値を指定

XSS-Protection: { "nonce": "abcdefg" }
<script src="script.js" nonce="abcdefg"></script>
reflection

反射型XSSに対するポリシーに対して、ignore, filter, blockを指定する。ignoreの場合はユーザエージェントの保護機能を無効にする、filterはユーザエージェントのXSS検知機能で、検知された場合はそのスクリプトの実行をブロックする。blockは、ユーザエージェントのXSS検知機能で検出された場合にそのページのロード自体をブロックする。

XSS-Protection: { "reflection": "block" }
report

Reporting APIに則ってレポートグループを指定する。
reportOnlyを指定することで、ブロックはせずレポートだけを送ることもできます。

XSS-Protection: { "nonce": "abcdefg", "report": "group1" }
Report-To: { "url": "https://example.com/report", "group": "group1", "max-age": 10886400 }

Chrome 56 のHTTPサイトへの日本語版警告

Moving towards a more secure web」でアナウンスされているように、2017年1月にリリースされる予定のChrome 56でHTTPサイトへの警告が表示されるようになる。


日本語のサイトでも取り上げられている

日本語版表示

Chrome Canaryで、chrome://flagsより 下記設定を有効にすることで、日本語版の警告表示が確認できる。

  • "保護されていない発行元に「保護されていない発行元」のマークを付ける"


http://asnokaze.com


https://asnokaze.com


日本語だと、また印象が変わる。HTTPS対応できてないサイトへの影響は結構ありそうである。