Tuesday, July 15, 2014

自社サーバと交信するスマホアプリにおけるサーバ証明書の発行手法について(SSL Pinningと独自CA再考)

■背景




■他社CAは信頼できるのか



■独自CAに問題はあるのか




■自社CAと他社CAのリスク評価




■SSL Pinningの是非




■2種類のSSL Pinning



■では、どうすれば良いか





■補足:優良なCAから証明書を買うだけではダメな理由

Tuesday, July 1, 2014

The JSON SQL Injection Vulnerability

tl;dr

Many SQL query builders written in Perl do not provide mitigation against JSON SQL injection vulnerability.

Developers should not forget to either type-check the input values taken from JSON (or any other hierarchical data structure) before passing them to the query builders, or should better consider migrating to query builders that provide API immune to such vulnerability.

Note: 問題の発見者による日本語での説明はこちらです.

Background

Traditionally, web applications have been designed to take HTML FORMs as their input. But today, more and more web applications are starting to receive their input using JSON or other kind of hierarchically-structured data containers thanks to the popularization of XMLHttpRequest and smartphone apps.

Designed in the old days, a number of Perl modules including SQL::Maker have been using unblessed references to define SQL expressions. The following example illustrate how the operators are being specified within the users' code. The first query being generated consists of an equality match. The second query is generated through the use of a hashref to specify the operator used for comparison.

use SQL::Maker;
my $maker = SQL::Maker->new(…);

# generates: SELECT * FROM `user` WHERE `name`=?
$maker->select('user', ['*'], {name => $query->param('name')}); 

# generates: SELECT * FROM `fruit` WHERE `price`<=?
$maker->select('fruit', ['*'], {price => {'<=', $query->param('max_price')}});

This approach did not receive any security concern at the time it was invented, when the only source of input were HTML FORMs, since it is represented as a set of key-value pairs where all values are scalars. In other words, it is impossible to inject SQL expressions via HTML FORMs due to the fact that there is a guarantee by the query parser that the right hand expression of foo (i.e. $query->param('foo')) is not a hashref.


JSON SQL Injection

But the story has changed with JSON. JSON objects are represented as hashrefs in Perl, and thus a similar code receiving JSON is vulnerable against SQL operator injection.

Consider the code below.

use SQL::Maker;
my $maker = SQL::Maker->new(…);

# find an user with given name
$maker->select('user', ['*'], {name => $json->{'name'}}); 

The intention of the developer is to execute an SQL query that fetches the user information by using an equality match. If the input is {"name": "John Doe"} the condition of the generated query would be name='John Doe', and a row related to the specified person would be returned.

But what happens if the name field of the JSON was an object? If the supplied input is {"name": {"!=", ""}}, then the query condition becomes name!='' and the database will return all rows with non-empty names. Technically speaking, SQL::Maker accepts any string supplied at the key part as the operator to be used (i.e. there is no whitelisting); so the attack is not limited to changing the operator. (EDIT: Jun 3 2014)

Similar problem exists with the handling of JSON arrays; if the name field of the JSON is an array, then the IN operator would be used instead of the intended = operator.

It should be said that within the code snippet exists an operator injection vulnerability, which is referred hereafter as JSON SQL injection. The possibility of an attacker changing the operator may not seem like an issue of high risk, but there are scenarios in which an unexpected result-set of queries lead to unintended information disclosures or other hazardous behaviors of the application.

To prevent such attack, application developers should either assert that the type of the values are not references (representing arrays/hashes in JSON), or forcibly convert the values to scalars as shown in the snippet below.

use SQL::Maker;
my $maker = SQL::Maker->new(…);

# find an user with given argument that is forcibly converted to string
$maker->select('user', ['*'], {name => $json->{'name'} . ''}); 


Programmers Deserve a Better API

As explained, the programming interface provided by the SQL builders including SQL::Maker is per spec. as such, and thus it is the responsibility of the users to assert correctness of the types of the data being supplied.

But it should also be said that the programming interface is now inadequate in the sense that it is prone to the vulnerability. It would be better if we could use a better, safer way to build SQL queries.

To serve such purpose, we have done two things:


SQL::QueryMaker and the Strict Mode of SQL::Maker

SQL::QueryMaker is a module that we have developed and released just recently. It is not a fully-featured query builder but a module that concentrates in building query conditions. Instead of using unblessed references, the module uses blessed references (i.e. objects) for representing SQL expressions / exports global functions for creating such objects. And such objects are accepted by the most recent versions of SQL::Maker as query conditions.

Besides that, we have also introduced strict mode to SQL::Maker. When operating under strict mode, SQL::Maker will not accept unblessed references as its arguments, so that it would be impossible for attackers to inject SQL operators even if the application developers forgot to type-check the supplied JSON values.

The two together provides a interface immune to JSON SQL injection. The code snippet shown below is an example using the features. Please consult the documentation of the modules for more detail.

use SQL::Maker;
use SQL::QueryMaker;

my $maker = SQL::Maker->new(
   …,
   strict => 1,
);

# generates: SELECT * FROM `user` WHERE `name`=?
$maker->select('user', ['*'], {name => $json->{‘name'}}); 

# generates: SELECT * FROM `fruit` WHERE `price`<=?
$maker->select('fruit', ['*'], {price => sql_le($json->{‘max_price’})}); 


Similar Problem may Exist in Other Languages / Libraries

I would not be surprised if the same proneness exist in other modules of Perl or similar libraries available for other programming languages, since it would seem natural from the programmers' standpoint to change the behaviour of the match condition based on the type of the supplied value.

Generally speaking application developers should not except that a value within JSON is of a certain type. You should always check the type before using them. OTOH we believe that library developers should provide a programming interface immune to vulnerabilities, as we have done in the case of SQL::Maker and SQL::QueryMaker.


Note: the issue was originally reported by Mr. Toshiharu Sugiyama, my colleague working at DeNA Co., Ltd.

Determining whether if a function (or variable, or constant) is declared in C++11

As @mattn_jp pointed out on his blog entry, C/C++ allow use of redundant parenthesis in variable declaration. For example, both of the following statements conform to the language specification.

int i = 3; // declares variable i, initialized to 3
int (j) = 4; // declares variable j, initialized to 4

By using this feature, it is possible to create a macro that tests the existence of whatever variable or constant.

The macro: is_defined in the following example is one such implementation.

Compiling and running the code would likely emit is NAN defined? yes. But if you remove #include <cmath> the output will likely change to false, since NAN is a constant defined in cmath.

#include <iostream>
#include <cmath>

struct is_defined_t {
  static bool defined_;
  is_defined_t() { defined_ = false; }
  template <typename T> explicit is_defined_t(const T&) { defined_ = true; }
};

bool is_defined_t::defined_;

#define is_defined(arg) (([]() { is_defined_t(arg); return is_defined_t::defined_; })())

int main(int argc, char **argv)
{
  std::cout << "is NAN defined? " << (is_defined(NAN) ? "yes" : "no") << std::endl;
  return 0;
}

Shortcoming of the example is that the result can be only obtained at runtime. It would be useful if we could get the result at compile time, since once we have such thing, we can get rid of autoconf, CMake, etc.

Friday, June 27, 2014

LINE「独自暗号化」のメリットと安全性について

LINEが使用している「独自の」暗号化手法について、情報が一部開示(参照:LINEの暗号化について « LINE Engineers' Blog)され、Twitterでもやりとりをしたので、まとめてみる。

■なぜTLSを使わないか

TLSではなく、1パスのメッセージ暗号化を使っている理由については、Adopting SPDY in LINE – Part 2: The Details « LINE Engineers' Blogに以下のような記載があり、TLSを使うことによるレイテンシの増加を懸念しているためと考えられる。
3G mobiles networks normally operate at slow speeds. (中略)If the connection changes modes when the user sends a message, we are forced to wait until we receive the server response before we can do anything else.

■どのような暗号化手法なのか

RSA暗号に加え、ブロックサイズが128bitの共通鍵暗号を用いており、共通鍵暗号の鍵をサーバのRSA公開鍵で暗号化し、共通鍵暗号で暗号化されたメッセージとともに送信していると考えられる(参考:ツイート 1, 2, 3, 4)。

おそらく、リクエストを送信する度に、大筋で以下のようなことを行っているのではないか。
# AES鍵を生成
AESKEY=`openssl rand -hex 16`注6

# 初期化ベクタを生成
IV=`openssl rand -hex 16`

# AES鍵をサーバの公開鍵で暗号化注1し、HTTPリクエストヘッダとして送信
echo -n "$AESKEY" | openssl rsautl -encrypt -pubin -inkey server-public.pem -in /dev/stdin | openssl base64

# POSTリクエストのボディをAESを利用して暗号化し、送信
echo -n "$content" | openssl enc -aes-128-cbc -in /dev/stdin -K "$AESKEY" -iv "$IV" | openssl base64

■この手法は安全か

RSAやAESといった暗号アルゴリズムは正しく使わないと脆弱になるし、どのように使っているかは今回開示されていない。とは言え、このように良く知られた部品(PKCS #1 v1.5とAES-CBC)を単純な形で組み合わせているのであれば、問題がある可能性は低い注2。実際のところ、上記推測例は、秘話化という観点からは僕には特に問題がないように思える。問題があるなら誰か教えてほしい注3

手法の推測が正しいならば、「通信全体について、他国からアクセスすることは不可能」注4ということだし、「システムに直接侵入するのではなく、通信回線とサーバーの間でワイヤタッピング」注5することにより情報が漏洩している可能性は極めて小さいのではないか。

■同様の暗号化を行いたい場合はどうすればいいか

ウェブアプリケーションなどで使用が容易なメッセージ暗号化手法については、現在、JSON Web EncryptionがIETFで標準化の最終段階にあります。完全性保護も含まれているし、標準化団体での安全性検証が行われているこちらを使いましょう。対応しているライブラリについてはJSON Web Token (JWT) - OAuth.jpが参考になるかもしれません。


注1: この例ではPKCS #1 v1.5を使用しているが、RSA-KEMが、より望ましいかもしれない
注2: CBCモードの使用法については、NISTが出しているガイドライン(NIST SP 800-38A)と、CRYPTRECによる安全性評価を参照
注3: リプレイ攻撃への対策や完全性保護については、この例には含めていない
注4: 参照:LINE、改めて傍受を否定 「暗号化後データは独自形式、解読は不能」 - ITmedia ニュース
注5: 参照:韓国国情院がLINE傍受:FACTA online
注6: 生成している鍵とIVの長さが64bitになっていたのを修正 (11:21am)

Thursday, June 5, 2014

Unix系OSの権限分離の変遷について(もしくはなぜ、アプリ単位の権限分離が求められるようになったか)

[ブコメした件について。大筋でおかしなことは書いてないと思いますが、出典は確認していません]

Unix系OSにおける権限分離は、伝統的に、利用者ごとに異なるuser idを割り振り、これを用いてアクセス制御を行うという方式で実現されてきた。また、デーモンプロセスについては、不要な権限付与を避け、デーモンプロセス間の相互作用を抑制するために、デーモンごとに専用の「user id」を発番するのが一般的な慣習とされるようになったという経緯がある。

しかし、2000年代に入ると、インターネットの普及とあいまって、クライアントサイドではこのような「利用者ごと」の権限分離では不十分という考え方がされるようになってきた。具体的には、

  • (オンラインバンクのパスワードに代表されるような)攻撃価値が高い情報をOS内に保存・利用するのが一般的になった
  • (限定された流通経路で入手するソフトウェアのみならず)インターネットからダウンロードしたプログラムを実行するという慣習が一般的になった
のようにユーザー行動が変化した結果、高いセキュリティが要求される処理と、セキュリティよりも利便性を重視したい処理が、同一のアクセス権限のもとに併存するようになったのである。だが、そのような状況は危険である(ネットからダウンロードしたマルウェアによって重要な情報が流出する可能性がある)。よって、同一ユーザー権限のもとで動作するプログラム間においても、アクセス権限の分離の必要性が認められるようになってきたのだ。

このため、iOSやAndroidのようなスマホOSにおいては、利用者ごとではなくプログラムごとに異なる「user id」を割り振り、互いのデータにアクセスできないような仕組みになっている。

一方でOS Xのような、より以前からあるUnix系OSにおいては、このような仕組みに移行することは現実的でないため、Keychainのように、秘匿性の高い情報についてのみプログラム単位でのアクセス制御機能を提供することで、問題の緩和が計られている。

以上のような経緯があるので、クライアント上で一般ユーザー権限で動作するプログラムにおいて、(psコマンド等を通じて情報が流出する問題がある)環境変数に認証情報を格納するのは避けるべきと言えるだろう。

Friday, May 23, 2014

[メモ] Perlのクロージャ生成速度は遅くない件

いくつかのスクリプト言語の処理系では、オブジェクトを生成して利用する場合と比較して、クロージャを生成する場合のオーバーヘッドが大きいという問題が知られています。最近、Perlでクロージャを使いたい場面に遭遇したので、ベンチマークをとってみることにしました。

結果、以下のように両者を使うアプローチで大きな速度差はないということがわかったのでメモ。

$ perl closure-vs-method.pl
         Rate  method closure
method  535/s      --    -12%
closure 609/s     14%      --
$ cat closure-vs-method.pl
use strict;
use warnings;
use Benchmark qw(cmpthese);

my $COUNT = 1000;

sub doit_closed {
    my $cb = shift;
    $cb->();
}

sub doit_method {
    my $cb = shift;
    $cb->meth();
}

cmpthese(-1, {
    'closure' => sub {
        for my $i (1..$COUNT) {
            doit_closed(sub {
                $i;
            });
        }
    },
    'method' => sub {
        for my $i (1..$COUNT) {
            doit_method(Holder->new($i));
        }
    }
});

package Holder;

sub new {
    my ($class, $value) = @_;
    return bless {
        value => $value
    }, $class;
}

sub meth {
    my $self = shift;
    return $self->{value};
}

Tuesday, May 20, 2014

[メモ] Apache+mod_sslでSIGBUSが発生した件

@hirose31さんと、Apache HTTPDからHTTPSでファイルダウンロード中にサーバプロセスがSIGBUSで死ぬって件にぶちあたり、

「OpenSSLの中でmemcpyがSIGBUSしてます」「な、なんだってー!」

って調べたのですが、理由は以下のとおりだった。

  • HTTPSの場合、デフォルト設定だとファイル読込にmmap(2)が使われる
    • mmapされたファイルのサイズが変更されてもApacheはそれを検知しようがない
    • そして、ファイル末尾以降のデータを読もうとするとセグメンテーションエラー(SIGBUS)が発生し、Apacheのサーバプロセスは異常終了する
  • HTTPの場合は、ローカルファイルシステムの場合sendfile(2)が使われるので、ファイルサイズが変更になってもApacheは異常終了しない
    • ただし、mod_deflateのような出力フィルタを使っている場合は、HTTPS同様に異常終了する可能性がある注1

この動作はApacheの「仕様」のようです注1日本語版だとNFS特有の現象のように読めるけど、英語版は以下のように書かれています。
Deleting or truncating a file while httpd has it memory-mapped can cause httpd to crash with a segmentation fault.
EnableMMAP Directive - core - Apache HTTP Server
言われてみれば当然だけど、mmapやsendfile等を自動的に使い分けているサーバの場合、これが原因だとは気づきにくいですよね。

というわけで、Apacheを使う場合はEnableMMAP Offにしておきましょう注1、さもないとアラートが上がってきたりしてウザいですよという話でした。本当は配信ファイルを全てアトミックに更新できばいいのですが、運用的にできないこともありますからね。

Apache以外のサーバでも同様の問題があるかどうかは知りません。

注1: HTTPSに限らない問題なので加筆修正 (17:06)。参照: Bug 46688 – Child segfault when mmaped file truncated