Saturday, September 8, 2018
次世代プロトコル(QUIC etc.)のセキュリティとプライバシー @ #builderscon
QUIC のハンドシェイクプロトコルとパケット番号暗号化、TLS の Encrypted SNI 拡張は、いずれも僕が提案した機能あるいは方式が採用される予定のものなので、背景にある動機や意義を含め、整理して発表する機会をもらえたことをありがたく感じています。
聴講いただいた方々、また、スライドをご覧になる方々と、次世代プロトコルの暗号応用の手法のみならず意義を含め共有し、理解と議論を深めることができれば、これに勝る喜びはありません。
PS. QUIC のハンドシェイクプロトコルと Encrypted SNI 拡張については、以下のブログ記事もあわせてご覧いただけます。
QUICハンドシェイクの再設計、もしくはTLSレイヤの終焉
TLS の SNI 暗号化に関する Internet Draft を共同提出しました
Tuesday, June 12, 2018
QUICハンドシェイクの再設計、もしくはTLSレイヤの終焉
提案者として、その背景にある考え方を整理したいと思います。
▪️提案内容
詳しくはDesign Docを見てもらえばいいとして、ざっくりいうと、
- TLSスタックをふたつに分割し
- パケットはQUICがレイアウトしたバイト列をTLSスタックが提供するAPIを使って暗号化注1して生成
- ハンドシェイクメッセージについては、平文のメッセージをTLSスタックとQUICスタックとの間で交換し、QUICスタック側で上記手法によるパケット化暗号化を行う
これにより、たとえばサーバがハンドシェイク時に送出するパケットの構造は以下のようにかわります。
赤は難読化(つまり正当なパケットと攻撃との区別がつかない)、黄は未認証の暗号化(通信は暗号化されているが、誰と話しているかはわからない)、青は認証済の暗号化を示しています。従来は5層のレイヤで2重の暗号化をしていたのが、4層のレイヤで1重の暗号化を行うようににかわることがわかります。
なぜ、このような変更を採用することになったのでしょうか。
▪️「レイヤ化」アプローチの限界
伝統的なTLSスタックにおいて、TLS接続はTCP/IP接続の拡張として表現されます。たとえば、JavaのSSLSocketはSocketを拡張したクラスです。
書き込みや読み込み命令はSSLSocketに対して同期命令として発行され、SSLSocketの中で適宜Socket I/Oに変換されます。
この手法の問題は、ソケットの管理が困難になりがちなことです。
たとえば、イベントドリブンなプログラムにおいては、同期I/Oではなく非同期I/Oが求められます。このことは、TLSスタックに非同期I/O用のモードを別途設けるか、あるいは、TLSスタックに対し、アプリケーション側からソケットのようなAPIをもつバッファを提供してやる必要がでてくることを意味します。
また、ヘッドオブラインブロッキングの影響を制御するために、暗号化するブロックサイズを制御し、ブロック単位で送信しようにも、そのような制御を可能にするインターフェイスが存在しないという問題があります注2。
これらの点に鑑み、H2Oでは、レイヤ化ではなく、入力バッファと出力バッファを引数としてハンドシェイクや暗号化関数を呼び出すというコーデックスタイルのAPIをもつ独自のTLSスタック「picotls」を開発し、TLS 1.3むけに採用してきました。
▪️「トランスポート層による暗号化」の必要性
TCP上でTLSを使う上での問題点のひとつが、第三者がいつでも接続をリセットできるという点です。このような攻撃が可能なのは、TCP自体が認証付き暗号によって保護されておらず、リセットを引き起こすようなパケットを誰でも生成できてしまうからです。
このようなトランスポート層への攻撃に対処するため、QUICのこれまでのドラフトでは、QUICの上で動くTLSスタックから鍵を「エクスポート」し、この鍵を使ってパケットを暗号化してきました。
この点を指して、picotlsの共同開発者であるChristian Huitema氏は「TLSの利用形態はレイヤからコンポーネントへ変化している」と指摘してきました。プログラミング上の都合のみならず、プロトコル設計上の都合からも、従来のTLSをレイヤとして利用するアプローチが限界に来ていたことがわかります。
利用形態の変化はともかく、これまでのドラフトのアプローチは以下の各点の問題を抱えるものでした。
- 暗号化をTLSとQUICという2つのソフトウェアスタックで行うという二重暗号化
- 鍵交換後もハンドシェイク完了まで攻撃に対し脆弱
- 鍵の使用開始タイミングが不明確
これらの問題を解決しようとする動きとしては、先行してIETF 101におけるEric Rescorla氏によるDTLSへの移行提案がありましたが、DTLSのパケットフォーマットがQUICが必要する機能を提供できない点や、再送制御がハンドシェイク前と後と異なってしまう点などが問題視されていました。
それを踏まえ、DTLSという、QUICとは異なるプロトコルを組み合わせるくらいなら、いっそTLSスタックを分割して、QUICのパケットフォーマットをそのまま使おう、というのが、今回の提案の骨子だったわけです。
従来のドラフトと比較した利点としては、
- 二重暗号化がなくなる
- TLS 1.3と同じタイミングで暗号鍵を変更するため、ハンドシェイク中の攻撃耐性が向上する注3
- 鍵管理をTLSスタックに依存できるようになるので、QUICスタックの品質向上に寄与する
- パケットフォーマットは引き続きQUICワーキンググループで設計管理、拡張される
また、この提案が受け入れられた背景として、TLSスタックの従来のAPIに限界があることが共通理解となっていた、という点もあげられるでしょう。
▪️まとめ
QUICではTLSをハンドシェイクと暗号化という2つの機能に分割して使用することになりました。このことは品質向上につながります。
TLSから見ると、TCP上での使用を前提としたTLS 1.3、UDP上での使用を前提に異なるパケットフォーマットを採用したDTLS 1.3に加え、第3のパケットフォーマットをもつプロトコルが誕生することを意味します。
今後策定されるプロトコルにおいて暗号化が必須であることを考えると、TLSのハンドシェイクメッセージを使いつつ、プロトコル毎に異なるパケットフォーマットを定義する流れが一般化するかもしれません。
長期的にみると、TLSの解体と再構成につながるかもしれませんね。
注2: TLSスタックの下にあるソケットAPIはあくまでバイト列をやりとりするためのものであって、レコードの切れ目がどこにあるかを表現する前提になっていない、ということ
注3: 図2において、赤で示される、パケット注入攻撃が可能なタイミングが短くなっていることが確認できます
Tuesday, May 1, 2018
IETF報告会で「TLS 1.3とその周辺の標準化動向」について発表してきた
TLS 1.3や関連する提案の技術的特徴とともに、スノーデン事件以来のテーマである通信内容のプライバシー保護とオシフィケーション(硬化)対策がどのように進んでいるか、ご理解いただける一助になれば幸いです。
なお、本スライドは会社のテンプレートを使用していますが、会社としての見解を表明するものではありません。
Tuesday, April 17, 2018
Fastlyのプログラマから見たCDN
当日は、リクルートさんに会場をご提供いただき、多くの方々、またAkamaiの方々ともと触れ合うことができる、とても貴重な機会となりました。この場を借りて、皆さんに御礼申し上げる次第です。
Thursday, March 23, 2017
JSON logging support is added to H2O HTTP/2 server version 2.2.0-beta3
Among the new features you will be finding in 2.2, in this blogpost I would like to talk about our support for JSON logging.
Traditionally, the log file format of HTTP servers have followed the tradition set by NCSA httpd more than twenty years ago. But the more we try to deal in various ways with the logs, the more it makes sense to use a standardized and extensible format so that we can apply existing tools to the logs being collected. Hence JSON.
Our support for JSON is a smooth evolution from the NCSA- (and Apache-) style logging. Configuration for a JSON logging will look like below.
access-log: path: /path/to/access-log.json format: '{"remote": "%h:%{remote}p", "at": "%{%Y%m%d%H%M%S}t.%{msec_frac}t", "method": "%m", "path": "%U%q", "status": %s, "body-size": %b, "referer": "%{referer}i"}' escape: jsonThe template specified by the
format
attribute uses the exact same specifiers as we use in NCSA-style logging. The only differences are that the non-substituted part of the template is JSON, and that another attributed named escape
is set to json
. The attribute instructs the logger to emit things in a JSON-compatible manner.Specifically, the behavior of the logger is changed to be:
- strings are escaped in JSON style (i.e.
\u00nn
) instead of\xnn
- nulls are emitted as
null
instead of-
The format may seem a bit verbose, but gives you the power to name the elements of a JSON object as you like, and to choose whatever format you want to use for compound values (e.g. the date, as shown in the example above).
When accessed by a client, a log line like below will be emitted for the above configuration.
{"remote": "192.0.2.1:54389", "at": "20170322161623.023495", "method": "GET", "path": "/index.html", "status": 200, "body-size": 239, "referer": null}One thing you may notice is that the value of the
referer
element is emitted as null
without the surrounding double quotes that existed in the specified format. When escaping in JSON style, h2o removes the surrounding quotes if the sole value of the string literal is a single format specifier (i.e. %...
) and if the format specifier evaluates to null. In other words, "%foo"
evaluates to either a string literal or null, while %foo
evaluates to a number or null.If a string literal contains something more than just one format specifier, then the values are concatenated as strings to form a string literal. So
"abc%foo"
will evalutate to "abcnull"
.The other thing that is worth noting is that the substituted values will always be escaped as ISO-8859-1. It is the responsibility of the user to convert the string literals found in the log to the correct character encoding. Such conversion cannot be done at HTTP server level since it requires the knowledge of the application being run. I would like to thank @nalsh for suggesting the approach.
Friday, February 5, 2016
H2O HTTP/2 server 1.7.0 released; added a new benchmark
Major additions in this update are:
- asynchronous HTTP client for mruby handler
- Support for CGI and Basic Authentication
- Support for wild-card hostnames
You can find more detailed description of the additions in my previous blogpost: H2O version 1.7.0-beta1 released with enhanced mruby scripting, CGI, and much more. And along with the new features, we have also enabled the use of Neverbleed by default, to reduce the risk of private key disclosure in case of a vulnerability.
We have also updated the numbers of the request-per-seconds benchmark.
I have seen many people hesitating to move to HTTPS or HTTP/2 in fear of performance issues. But what is apparent from this benchmark is that the performance of HTTP/2 with TLS is actually on par with HTTP/1 without TLS (in case of nginx) or much faster (in case of H2O).
Details of the benchmark (along with other benchmarks) can be found at h2o.examp1e.net/benchmarks.html.
Thursday, June 18, 2015
H2OとPHPを組み合わせるの、超簡単です(もしくはmod_rewriteが不要な理由)
ありがたやありがたや。
その中で、
いやはや、まったくそのとおりなわけです。http://hoge/entry/1
みたいなのをphpにマップする方法はまだよくわかってません。その内しらべます
github.comを読む限り
FastCGI (or PHP) applications should be as easily configurable as it is for the Apache HTTP serverということで、やったぜ!ってなるんですけど、nginxはもとより、Apacheにおいても現状ルーターをつかっているようなアプリだとhtaccessをいちいちかかないといけないので、Apacheみたいなスタイルが楽なのか?というとちょっと疑問があります。
(たとえば以下みたいなの)
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [QSA,L]ここらへんがスッっとかけないと、まあApache、nginx同様にH2Oの設定のタレが必要になるかなあ、という感じはあります。
ちなみにこの手のことをNginxでやろうとするともっと難しくて、以下のように
try_files
やfastcgi_split_path_info
を使わないと脆弱性が発生するなんて話があるそうです(参照: Setting up PHP-FastCGI and nginx? Don’t trust the tutorials: check your configuration! » Neal Poole)。これ、みんなちゃんとできてるんですかね?# Pass all .php files onto a php-fpm/php-fcgi server. location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; include fastcgi_params; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass php; }
こんな難しい設定、書けるわけないだろうが!!!!!!!
というわけで、H2Oの場合はもっと簡単に設定できるようになっています。以下のような感じ。
paths: "/": # /path/to/doc-root以下の静的ファイルを返す(存在した場合) file.dir: /path/to/doc-root # 存在しなければ、/index.php/ に内部転送 redirect: url: /index.php/ internal: YES status: 307
単純ですね。どうなってるかはコメントを見れば自明かとも思いますが、以下解説します。
実は、H2Oの設定ファイルにおいては、1つのパスに複数のハンドラを設定することができるのです。
複数のハンドラが設定されている場合、H2O は有効なレスポンスが生成できるまで、ハンドラを順次実行していきます。つまり、コメントにあるように、ファイルが存在すればそれを返すし、存在しなければ /index.php/ 以下にリクエストを内部的に転送し、その転送した結果の応答をクライアントに返すわけです。
わかりやすいですね。簡単ですね。
少し理屈っぽい話をすると、mod_rewriteのようなリクエストを書き換える黒魔術は、おおむね3種類の目的に使われてきました。第1の目的は、複数のローカルのディレクトリツリーを重ね合わせてひとつのhttpサイトとして表示すること。第2の目的は、uzullaさんが指摘したような、特定の条件をもつ以外のURLの処理を、FastCGIやアプリケーションサーバに委譲すること。第3の目的は、User-Agentや言語設定によって異なる応答を返すこと。
ですが、前2者を実現するためだけならば、正規表現を用いてURLを書き換えるような手法は過剰にすぎて管理がしづらかったわけです。H2OにおけるPHP対応においては、この点を意識し、複数のハンドラの連鎖を可能にすることで、設定が安全簡潔にできるようにしたのでした。
ちなみに、uzullaさんの例では
php-fpm
を使っていますが、H2Oはphp-cgi
を自分で管理することができるので、小規模なウェブサイトにおいては、file.custom-handler: extension: .php fastcgi.spawn: "PHP_FCGI_CHILDREN=10 exec /usr/bin/php-cgi"のように書くと、別途FastCGIデーモンを管理する必要がなくなって、より幸せになれるでしょう(名前は
php-cgi
でも、この設定においてはFastCGIとして動作します)。最後になりますが、ここで説明したFastCGI対応機能を盛り込んだH2Oバージョン1.3は今朝方リリースされたので、PHPerの皆様もそれ以外の皆様も、お試しいただければ幸甚に存じます。
リンク先のリリース告知エントリにも書いてあるけど、他のウェブサーバより表示速度(first-paint time)が、かなり速いですよ!
Thursday, May 14, 2015
jailing - chroot jailを構築・運用するためのスクリプトを書いた
でも、そのためだけにVPSにdockerとかコンテナを入れて使うってのは、構築も運用もめんどくさいし、ディスク容量食うし、やりたくない。systemd-nspawnも割と重たい雰囲気だし、LTSなubuntuだとそもそもsystemd入ってないし…
俺たちがほしいのは、ホストの環境の一部のみにアクセスできる、手軽なjailだー! ってわけで、ざっくり書いたのが、jailing。
/usr/bin等、OS由来のディレクトリをchroot環境にread-onlyでエクスポートしつつ、指定されたコマンドを、そのchroot環境で動かすスクリプトです。
/usr/localや/homeといったディレクトリはエクスポートしないので、chroot環境下のソフトウェアにセキュリティホールがあって侵入されたとしても、(カーネルにバグがなければ)chroot環境外の情報が漏洩することはありません。
ホストとchroot環境でディレクトリを共有するためには、--bindオプションを使います。
たとえば、/usr/local/apache下にインストールしたApacheをchroot環境下で動かしたいなって時、jailingを使えば、以下のようにコマンド一発でchroot環境を作成して実行できます。
% sudo jailing --root=/var/httpd-jail \ --bind /usr/local/apache \ -- \ /usr/local/apache/bin/httpd \ -c /usr/local/apache/conf/httpd.confあるいは、/usr/local/h2o下にインストールしたH2Oをchroot環境下で動かす場合は、こんな感じ。
% sudo jailing --root /var/h2o-jail \ --bind /usr/local/h2o \ -- \ /usr/local/h2o/bin/h2o \ -m daemon \ -c /usr/local/h2o/etc/h2o.confあるいは、jail内に入るには、
% sudo jailing --root /var/h2o-jail \ -- \ bashとかやればいいわけです。
簡単ですね!
詳しくは
man jailing
したりしてください。それでは〜
Thursday, February 19, 2015
H2O, the new HTTP server goes version 1.0.0 as HTTP/2 gets finalized
According to mnot’s blog: HTTP/2 is Done posted today,
The IESG has formally approved the HTTP/2 and HPACK specifications, and they’re on their way to the RFC Editor, where they’ll soon be assigned RFC numbers, go through some editorial processes, and be published.Web browser developers have already implemented the protocol. Mozilla Firefox is already providing support for the HTTP/2 draft. Google has announced that they would turn on support for HTTP/2 on Chrome within weeks. Internet Explorer 11 on Windows 10 Technical Preview also speaks HTTP/2.
Considering the facts, it seemed that we'd better freeze the configuration directives of H2O now, so that people could rely on the software for serving HTTP/2 requests (note: the library API should still be considered unstable).
Features provided by H2O version 1.0.0 include the following; please refer to the README and `--help` for more information.
- support for HTTP/1.x and HTTP/2
- static file serving and reverse proxy
- HTTP/2 server-push
- excellent performance outperforming Nginx
- graceful restart and self-upgrade via Server::Starter
Started last summer, H2O is still a very young project. We would never have advanced this fast without so much help from the community (the fact is clear especially regarding the support for HTTP/2 if we look at H2O issue #133 as an example). I would like to express my gratitude for their advises and suggestions.
We plan to continue improving H2O rapidly. The primary focus is on performance, ease-of-use, and flexible (even autonomous) reconfiguration that suites the cloud era.
Today, HTTP is facing challengers. With the rise of smartphone apps, it is no longer the only protocol that can be used. But wouldn't it be better if we could all continue using a single, well-known protocol a.k.a. HTTP?
Our goal is by providing an excellent implementation, to keep the protocol as the primary choice of the developers, and furthermore, to expand the adoption of HTTP even more than before.
Stay tuned!
Monday, December 8, 2014
64bit時代のバッファ処理
1990年代後半から2010年頃までは、メモリ空間の大きさ(32bitすなわち4GB注1)を超える大きさのファイルを扱う時代でした。このため、httpdなどのサーバプログラムにおいても、入出力データをいったんテンポラリファイルとしてバッファリングする必要がありました。ですが、ファイルI/Oはメモリアクセスと比べると低速です。このため、小さなサイズのデータについてはメモリアクセスする一方で、大きなサイズのデータについてはファイルI/Oを用いる、という煩雑なコードを書く必要がありました。
しかし、2014年も暮れとなる今 、サーバサイドにおいては64bit環境のみを考えれば良い時代に入りつつあります。
もちろん、64bit環境といったところで、64bit空間の全てをユーザプロセスが使えるわけではありません。現行のx86-64 CPUがサポートする論理アドレス空間は48bit(256TB相当)であり、多くのOSではその上位アドレス半分がカーネル空間に割り当てられ、残った128TBがユーザプロセスで使用可能な空間となっています注2。
128TBものメモリ空間があれば、全てのテンポラリデータを「メモリ空間にマッピングして」使用することができます。
実際に、H2Oのバッファ処理では、64MBを超えるものについてはテンポラリファイルをftruncate/mmapすることで領域を確保注3し、これに対してメモリアクセスを行うようになっています。詳しくは
h2o_buffer_reserve
関数の定義をご覧ください。バッファを利用する側のコードにおいては、サイズの大小に関係なく単なるメモリブロックとしてアクセスできるため、コードが単純かつ高速になるのです。
※本記事はH2O Advent Calendar 2014の一部です。
注2: 参照: x86-64 - Wikipedia, the free encyclopedia - Operating system compatibility and characteristics
注3:
malloc(3)
で確保可能なメモリの総量である物理メモリ+スワップ領域の和は必ずしも十分に大きくないため、テンポラリファイルを生成してftruncateすることでディスク上に必要な領域を確保していますFriday, October 10, 2014
[メモ] root権限でrsyncする方法
そういった際には、sudo の -A オプションと rsync の --rsync-path オプションを使うと良いようです。
まず、リモートサーバに、パスワードを標準出力に出力するスクリプトファイルを配置します(ファイルのパーミッションを厳しくするのを忘れずに)。
% cat > bin/askpass #! /bin/sh echo "{{my_password}}" % chmod 700 bin/askpass %そして、rsync を実行する際には --rsync-path オプションを使い、リモートサーバの rsync を sudo -A 経由で起動するようにすれば良いのです。
% sudo rsync -avz -e ssh \ --rsync-path='SUDO_ASKPASS=/home/remote-user/bin/askpass sudo -A rsync' \ remote-user@remote-host:remote-dir local-dir
これは簡単!
sudo -Aオプションはパスワードを記述したファイルを設置する必要があるという点でNOPASSWD同様セキュリティの懸念はありますが、使いどころを誤らなければ便利だなと思いました。
Tuesday, July 15, 2014
自社サーバと交信するスマホアプリにおけるサーバ証明書の発行手法について(SSL Pinningと独自CA再考)
自社のサーバと通信する自社アプリについて、本来不要であるにも関わらず他社であるCAに認証情報の管理を委託することが多いわけですが、CAが証明書を誤発行した結果情報漏洩が発生したとして、その責任は自社にはないと主張できるのか、もう一度考えなおしたほうがいいんじゃないかと思うんです
— Kazuho Oku (@kazuho) July 15, 2014
.@ockeghem @smbd @ando_Tw スマホアプリの提供においてはコードの署名鍵を安全に管理することが求められますが、その前提において通信相手の認証管理をCAに委託することにどれほどの意味があるんでしょう
— Kazuho Oku (@kazuho) July 14, 2014
■他社CAは信頼できるのか
特定のCAにpinningするのはしないより安全だけど、そもそも誤発行しないCAなんてあるのかという議論は重要。見知らぬ他社間のやりとりで認証するわけだし、過去にはMicrosoftの証明書を「VeriSignが」誤発行した事例もある
— Kazuho Oku (@kazuho) July 14, 2014
■独自CAに問題はあるのか
独自CAの問題は、CA証明書の安全な配布が困難な点とCAがあらゆるドメインに対して証明書を発行可能な点の2点だったが、前者は証明書をバンドルして安全に配信できるスマホアプリにおいては問題にならないし、後者についても、そもそも特定のサーバしか交信しないなら関係ない
— Kazuho Oku (@kazuho) July 15, 2014
@kazuho また、CA証明書の署名可能なドメインを限定する技術も普及してきている http://t.co/S8lDQLaSct
— Kazuho Oku (@kazuho) July 15, 2014
■自社CAと他社CAのリスク評価
話を戻すと、他社CAを使う上でのリスクは、CAによる誤発行+自社をCAに対して認証するクリデンシャルの管理。自社CAを運用する上でのリスクは、CAの秘密鍵の漏洩。いずれのケースでも社内の秘密情報の管理は必要だし、他社CAの場合の方が攻撃が成功する可能性は高いと思う(続く)
— Kazuho Oku (@kazuho) July 14, 2014
@kazuho 一方で、オーディティングとリヴォケーションについては他社CA利用に優位性がある
— Kazuho Oku (@kazuho) July 14, 2014
■SSL Pinningの是非
特定サーバーとしか通信しないアプリが有象無象のCAを信用するのはリスクだし実際事故も起きてるけど、それを防ぐためにユーザーが許可した通信監視まで出来なくなるような実装がまかり通っていて、それはユーザーの自由を損ねていると思うのですよ
— mala (@bulkneets) July 14, 2014
情報漏洩の防止と利用者の同意に基づいた傍受の容易さを比較した場合、前者を優先すべきなのは当然だと思うし、SSLにおけるpinningの実装手法において後者への配慮が望ましいとしても、それがpinningすべきでないという理由にはならない
— Kazuho Oku (@kazuho) July 14, 2014
■2種類のSSL Pinning
SSL Pinningについて、サーバ証明書とCA証明書のどちらをpinすべきかという点については、前者は鍵の追加・廃止・更新が困難(heartbleedのような場合にユーザ側での対応が必要)という可用性の問題が、後者はCAによる誤発行のリスクをゼロにできないという問題がある
— Kazuho Oku (@kazuho) July 15, 2014
■では、どうすれば良いか
X.509証明書に複数の署名(自社の署名とCAの署名)をつけて両方を検証すれば、誤発行のリスクも極小化できるし、オーディティングも可能なので、pinningよりも良いのではないかと思いますね(現状のX.509仕様では迂遠な実装にならざるを得ないけど)
— Kazuho Oku (@kazuho) July 15, 2014
自社アプリしかアクセスしないサーバなのか、ウェブブラウザからもアクセスされるサイトなのかによって、一般的なCAによって署名されたサーバ証明書が必須かどうかは変わるけど、ともかく、他社CAに全面的に依存するのが「正しい」という考え方は改めるべき時期にきてるんじゃないかと思う
— Kazuho Oku (@kazuho) July 15, 2014
■補足:優良なCAから証明書を買うだけではダメな理由
.@nalsh ウェブブラウザのSSLにおいて「VerisignならOK」と言えるのは、ユーザが証明書発行者を確認するUIをブラウザが提供しているからです。Verisignのような特定のCAに依拠した安全性をスマホアプリで提供したいなら、同様のUIかpinningの実装が必要です
— Kazuho Oku (@kazuho) July 15, 2014
Thursday, November 21, 2013
パスワードが漏洩しないウェブアプリの作り方 〜 ソルトつきハッシュで満足する前に考えるべきこと
徳丸さんのスライド「いまさら聞けないパスワードの取り扱い方」に見られるように、昨今、ウェブアプリケーションの設計要件として、サーバ内に侵入された場合でもユーザーのパスワードをできるだけ保護すべきという論調が見受けられるようになってきました。
上掲のスライドでは、その手法としてソルトつきハッシュ化を勧めています。しかしながらスライドに書かれているとおり、ソルトつきハッシュには、複雑なパスワードの解読は困難になるものの、単純なパスワードを設定してしまっているユーザーのパスワードについては十分な保護を提供できないという問題があります。そして、多くのユーザーは適切なパスワード運用ができない、というのが悲しい現実です。
ソルトつきハッシュを使った手法でこのような問題が残るのは、ウェブアプリケーションサーバに侵入した攻撃者がユーザーの認証情報をダウンロードして、認証情報をオフライン攻撃することを防ぎようがない、という前提を置いているからです。
逆の言い方をすると、攻撃者がアプリケーションサーバに侵入したとしてもユーザーの認証情報にアクセスできなければ、認証情報を奪われる可能性はないわけです。そのようなシステムを構築するにはどのようにしたらいいでしょうか。
一般的なウェブアプリケーションにおける答えは、ストアドプロシージャの利用とデータベースサーバの保護にあります注1。
■■ストアドプロシージャによるパスワード認証
多くのSQLデータベースサーバはストアドプロシージャとストアドファンクションをサポートしており、これはMySQLやPostgreSQLといったオープンソースRDBMSでも例外ではありません。ストアドプロシージャやストアドファンクションには二つの役割があり、ひとつは処理手順をまとめること、もうひとつは、テーブルへのアクセスパターンを限定することです。
たとえばMySQLの場合、以下のようなDDLを実行することで、一般ユーザー権限(webapp)からは読み書きできない認証情報カラムpasssalt(ソルト値を格納)とpasshash(ハッシュ値を格納)をもつuserテーブルを作ると同時に、パスワードの一致を確認するストアドファンクション(check_pw)を提供することができます。
-- テーブル定義 CREATE TABLE user ( username varchar(255) NOT NULL, passsalt varbinary(255) NOT NULL, passhash varbinary(255) NOT NULL, PRIMARY KEY (username) ) DEFAULT CHARSET=utf8; -- パスワードを検証するストアドファンクションを定義 DELIMITER | CREATE FUNCTION check_pw(u TEXT, p TEXT) RETURNS INT BEGIN RETURN (SELECT COUNT(*) FROM user WHERE username=u and passhash=SHA1(CONCAT(passsalt,SHA1(p)))); END; | DELIMETER ; -- パスワードを更新するストアドプロシージャ定義 DELIMITER | CREATE PROCEDURE update_pw(u TEXT, oldpass NEXT,newpasssalt TEXT,newpasshash TEXT) BEGIN DECLARE r INT; SET r = check_pw(u, oldpass); IF r = 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT 'incorrect old password' ELSE UPDATE user SET passsalt=newpasssalt,newpasshash=newpasshash WHERE username=u; END IF; END; | DELIMETER ; -- webappユーザーにusernameカラムのみSELECT権限付与 GRANT INSERT,DELETE ON db.user TO webapp@'webapp-host'; GRANT SELECT (username) ON db.user TO webapp@'webapp-host'; -- webappユーザーにストアドの実行権付与 GRANT EXECUTE ON FUNCTION check_pw TO webapp@'webapp-host'; GRANT EXECUTE ON PROCEDURE update_pw TO webapp@'webapp-host';
実際に、このデータベースに一般ユーザー権限で接続してみると、以下のように、ユーザーの作成はできるものの認証情報は読めない一方で、パスワードの検証は可能、となっていることがわかります。
% mysql -u webapp db -- 新規ユーザー johndoe (パスワード: johnpass)を作成 mysql> insert into user (username,passsalt,passhash) values ('johndoe','abcdefg',sha1(concat('abcdefg',sha1('johnpass')))); Query OK, 1 row affected (0.01 sec) -- userテーブルの全カラムを読むことはできない mysql> select * from user; ERROR 1142 (42000): SELECT command denied to user 'webapp'@'webapp-host' for table 'user' -- usernameカラムだけならば読むことができる mysql> select username from user; +----------+ | username | +----------+ | johndoe | +----------+ 1 row in set (0.00 sec) -- パスワードの検証は可能 mysql> select check_pw('johndoe','johnpass'); +--------------------------------+ | check_pw('johndoe','johnpass') | +--------------------------------+ | 1 | +--------------------------------+ 1 row in set (0.00 sec) -- 間違ったパスワードを指定すると検証に失敗 mysql> select check_pw('johndoe','badpass'); +-------------------------------+ | check_pw('johndoe','badpass') | +-------------------------------+ | 0 | +-------------------------------+ 1 row in set (0.00 sec)
このように、SQLデータベース内にパスワードの検証/更新ロジックを持たせることで、(データベースに一般ユーザー権限でアクセスするための情報が保存された)ウェブアプリケーションサーバに攻撃者の侵入を許した場合でも、パスワードの漏洩を防ぐことができます。
また、ストアドプロシージャで監査ログを出力することで、攻撃を検知したり被害範囲を特定したりといったことも可能になるでしょう。
(2013/11/27追記: このような手法を実践するにあたっては、@isidaiさんの指摘をあわせて参照の上、各自の責任において設計、実装くださいますようお願いいたします)
■■データベースサーバの保護
SQLデータベースの機能を利用してパスワードを保護する場合に考えなければならないこととして、ウェブアプリケーションサーバが攻撃者の手に落ちた場合に、そこを踏み台としてデータベースサーバに侵入されるリスクを下げるにはどうすれば良いか、という点があります。
答えは単純で、データベースサーバをウェブアプリケーションサーバとは別のネットワークセグメントに配置し、両者の間にファイアウォールを設置すれば良い注2、ということになります。ファイアウォールの役割は、ウェブアプリケーションサーバからデータベースサーバへのアクセスを正規のTCPポートを指定するもの以外、全て遮断することです注3。
このように設定することで、データベースサーバの権限管理機能にバグがない限り、ウェブアプリケーションサーバを踏み台として認証情報を奪われる可能性はなくなります。
なお、言うまでもありませんが、データベースサーバを配置するネットワークセグメントをインターネットと直接接続してはいけません。理想的には、アクセス手段を厳密に規定して管理系セグメントからのみ接続可能とすべきでしょう。
■■まとめ
ユーザーのパスワードを適切に暗号化することは重要ですが(そのまとめとして徳丸さんのスライドは優れていると思います)、サーバへの侵入を前提としてアーキテクチャを設計する際には、多層防御の手法が有効になります。
本稿では、RDBMSサーバのストアドプロシージャとファイアウォールの使用を通じて、ユーザーの認証情報を多層防御するウェブアプリケーションが簡単に構成できることを説明しました。
■■余談
実は、本稿で取り上げた問題とその解は、Unixにおいてシャドウパスワードが導入された経緯の変奏曲です。Unixのシャドウパスワードとは、誰もが閲覧可能なファイル(/etc/passwd)に一方向暗号化(ハッシュ化)されたパスワードを記載しておくのではなく、攻撃者(一般ユーザー)がアクセスし得ないファイル(/etc/shadow)に認証情報を配置し、認証が必要な場合はAPIを通してルート権限で動作するプロセスに問い合わせ、/etc/shadowの内容と比較した結果を回答してもらう、という仕組みです。
注2: ウェブアプリケーションとデータベースが1台のサーバに同居するケースでも、OSのユーザベースの権限分離を使って同様のことは不可能ではないと思います
注3: 大規模な構成かのように聞こえるかと思いますが、Amazon AWSのRDSを使うと必然的にこの構成になるなど、実は意外と身近なものです
Thursday, April 28, 2011
Webアプリケーションの無停止稼働 - Server::Starter, Parallel::Prefork, Starlet を使って (SoozyConference 7 発表資料)
1月に開催された SoozyConference 7 の発表資料です。