Wednesday, November 27, 2013

Do not allocate Typed Arrays too frequently, or your browser will die OOM

The following code causes the browsers' JavaScript runtime to crash (or allocate gigabytes of memory) when the array length is only around 1MB to 2MB (reproducible on Google Chrome 30.0.1599.101 / Safari 6.1 (8537.71) running on OS X 10.8.5; Mozilla Firefox 25.0.1 does not have the problem).

current length of Uint8Array is <input id="arrlen"> bytes.
<script>
var arr = new Uint8Array(12345);
function doit() {
  for (var i = 0; i < 100; ++i) {
    arr = new Uint8Array(arr.length + (Math.random() * 500 | 0));
  }
  document.getElementById("arrlen").value = arr.length;
}
setInterval(doit, 1);
</script>

test link

My understanding is that historically, this is due to the fact that Typed Arrays were defined outside of the ECMAScript specification and that it was implemented outside of the JavaScript VMs. Memory chunks that are used to store the elements of the arrays are allocated outside of the VM heap, and GC tracks only the reference to the array elements. Thus the pressure against GC is tiny per each allocation even though the pressure against malloc might be high. And the outcome is that malloc gives up well before GC starts to free the typed arrays that are no longer used.

I had thought that the problem had been fixed a while ago, but it still seems to exist. And although I believe that it will be fixed sooner or later I am writing this weblog so that, until then, others who fall into the pitfall can understand the problem.

PS. opened the issue on the Chromium issues tracker.

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の内容と比較した結果を回答してもらう、という仕組みです。


注1: 厳密に言うと、RDBMSでなくても別のサーバにAPIで問い合わせるのであればなんでもいいのですが
注2: ウェブアプリケーションとデータベースが1台のサーバに同居するケースでも、OSのユーザベースの権限分離を使って同様のことは不可能ではないと思います
注3: 大規模な構成かのように聞こえるかと思いますが、Amazon AWSのRDSを使うと必然的にこの構成になるなど、実は意外と身近なものです

Friday, November 15, 2013

もうひとつの知られざるオープンソース 〜 ウェブ企業のOSS戦略

「オープンソースソフトウェア(OSS)」と聞いて、あなたがイメージするものはなんですか? 多くの人は Linux や Apache、Firefox といった成功した大規模なソフトウェア製品を思い浮かべることでしょう。

実は、ウェブ上でサービスを提供する会社のエンジニアたちは、これらとは別の種類のOSSを使って仕事をしています。このブログエントリでは、そのようなOSSを紹介し、それらがなぜ開発され使われているかを説明したいと思います。


■ウェブ企業におけるOSS開発の実例と合理性

下の図は、Perl で記述される大規模ウェブアプリケーションの一般的な構成を示しています注1。このうち、「自社ロジック」と書かれているところ以外は、全てオープンソースとして開発/公開されているモジュール(ソフトウェア部品)です。各社のエンジニアが密接に協力しながら、ミドルウェアをオープンソースとして整備していることがわかるかと思います注2


このような部品開発における会社を超えた協力体制は、Perl以外のプログラミング言語を使っているウェブ企業でも良く見られるものです。

いったいなぜ、そのような協力がなされるのでしょうか。

「そのようなエンジニア文化だから」というのもひとつの答えかと思います。ですが、彼らが業務の一環として開発したミドルウェアをオープンソースとして公開している以上、文化を裏打ちする経済的合理性を考えないわけにはいきません。

ウェブ業界は、主にコンテンツをユーザーに配信する企業群として成長してきました。これらの企業におけるコアコンピタンスとは、コンテンツ(あるいはコンテンツを提供してくれるユーザー)です。このため、ソフトウェア自身を金の卵だと考えるソフトウェア業と異なり、ウェブ業界では、ソフトウェアはコンテンツを配信するための手段だという位置づけをされることが多々あります。そして、ソフトウェアが売上をあげるための手段である以上、その開発/運用コストを削減することが重要になります注3

種々多様なコンテンツにあわせて迅速にソフトウェアインフラを整備し安価に運用するためには、カスタム開発を最小限に抑える必要があります。また、社外への発注や人材の流動、ビジネス規模の変化を考慮した場合、ソフトウェアスタック全体を内製し社内で維持改善を続けるよりも、業界全体のデファクトスタンダードとなる実装が自社の都合にあった形で存在することが望ましいと言うことができます。

図で示したソフトウェア構成は、この経済的な要求が反映された結果なのです。

たとえば、Furlを見てみましょう。2010年に開発が開始されたFurlは、LINEに勤務するエンジニアがメンテナンス権をもっている高速かつカスタマイズが容易なHTTPクライアントであり、サーバの必要台数がコストに響くような大規模サービスで好んで使われています。一方で、その開発には、当初からDeNAに所属するエンジニアが参加しており、同社の影響も大きいものがあります。その顕著な例が、DeNAの要件にあわせて追加された、DNSリゾルバの置換機能です。

一般的な使用形態において必要ない機能というのは、第三者が、たとえ必要なコードを書いて追加をお願いしたとしても、なかなか受け入れてもらえるものではありません。それは、機能の追加をいったん受け入れてしまうと、以後その機能をメンテナンスしていく責務が主開発者の肩にかかるからです。にもかかわらず、Furlに同機能が追加されたのは、両社のエンジニアが継続的に共同作業を行っており(Furlが、DeNAのエンジニアがメンテナンスしているHTTPパーサライブラリを使っていることを含みます)既に信頼関係が構築されていたという事情を勘案する必要があるでしょう。

このように、継続的にオープンソースのミドルウェア開発に参加することでコストを分担しつつ、自社にとって便利なものにしていく、という営みが実際に行われているのです。


■OSS共同開発のリスク評価

では、経済的な合理性はあったとして、部品を共同開発することによるリスクはないのでしょうか。よくある質問は「ある会社の社員が、ライバル社に害をなすような機能を組み込んだりしないのですか」というものです。ウェブ企業のエンジニアたちによるオープンソース開発では、この点についても抑止力が働く構造になっています。

本稿で例に挙げたOSSはいずれも、誰がどの機能を書いたか、コミットログを追うことで全て把握できるようになっています。そして、エンジニアのコミュニティにおけるコードの品質に対する最初の評価基準は、どの会社のエンジニアが書いたか、ではなく、誰が書いたか、という点です注4

この条件下において、悪意のあるコードをOSSに埋め込むことは、エンジニアにとってキャリアの終わりを意味します。そのような行為に手を染めたエンジニアは、以後就職先を見つけることはできないでしょう。終身雇用が期待できる業種なら、それでも悪行を働くインセンティブがあるかもしれませんが、ウェブ業界は転職を前提としたキャリアプランを立てざるをえないところです。このように、職業倫理のみならず経済的側面からも、共同開発によるリスクは抑えられていると言うことができます。実際には、コードが多くの目にさらされることによる品質向上効果の方が大きいと考えるべきでしょう。


■まとめ

以上、述べてきたように、ウェブ企業におけるオープンソース開発は、サポート収益を期待するソフトウェア企業のオープンソース注5とは大きく異なる原理に基づいて活発な開発が行われている分野です。コンシューマには見えない小粒なソフトウェアが多いがゆえに見落とされがちな分野ですが、オープンソースソフトウェアに関する議論や検討を行う際には、これらの活動とその背景についても理解しておくことが望ましいかと思います。

注1: 主要なモジュールと本稿に登場するものを中心に抜粋して図示しています。実際には、他にも多くの人が開発した様々なOSSモジュールが使用されています
注2: 会社の枠を超えた恊働の実態とOSSを使うに際してコミッターの所属を気にする必要がないということを説明するためにあえて社名を出しているだけなので、その点、ご理解とご配慮をいただけると幸いです
注3: 主目的がサポート収益の最大化ではなくコードの共有によるコストの削減であるため、ウェブ企業が公開するOSSについては緩やかなOSSライセンスが選択される傾向にあります(Perlモジュールの場合はPerl License(Artistic LicenseとGPLのデュアルライセンス)が通常)
注4: 品質の高い貢献が従来から多い人のコードは信頼される一方、新規参入者のコードは注意深く検査されるという意味です
注5: ソフトウェア企業の(OSSを含む)競争戦略については、やや古いですが「ソフトウエア企業の競争戦略」(マイケル・A.クスマノ)が良書だと思います