NoSQLの一部の製品は、クライアントからサーバに送るクエリにスクリプト言語を使用できるようになっています。スクリプト言語は、通常の演算子では表現しきれない複雑な絞り込み条件を表現する、などの用途で使われます。MongoDBにおいては、スクリプト言語としてJavaScriptが使用可能です。
スクリプト言語をサポートするNoSQL製品においては、不適切なプログラミングにより攻撃者が挿入したスクリプトがサーバ側で実行される危険性があります。サーバで実行されるJavaScriptコードに対するインジェクションは、SSJI(Server Side JavaScript Injection)とも呼ばれます。
下記は、あるWebサイトの商品検索のプログラムの一部です。
// リクエストパラメータから型番を取得 $kataban = $_GET['kataban']; // 絞り込み用のJavaScript関数を作成 $query = <<<JSQUERY function() { // 大文字化して部分一致検索を行う var input = '{$kataban}'.toUpperCase(); return this.kataban.indexOf(input) >= 0; } JSQUERY; // 作成した関数で絞り込み検索 $cursor = $db->products->find(array('$where' => $query));
パラメータとして受け取った$kataban(商品型番)を、JavaScriptコードに埋め込んでいます。検索を実行している行では「$where」というMongoDBの演算子を使っています。「$where」が連想配列の最上位のキーに指定された場合、その値は絞り込みを行うJavaScriptコードとして解釈・実行されます。このJavaScriptは、MongoDBが持つJavaScriptエンジン(SpiderMonkey)によってデータベースサーバ上で実行されます。
正常なパラメータは以下のような値です。
正常パラメータ例: kataban=ABX
このケースでは、入力データがデータベースサーバで実行されるJavaScriptに直接埋め込まれるため、攻撃者はリクエストパラメータを操作することで、任意のJavascriptをサーバ側で実行させることができます。攻撃の選択肢はいろいろとありますが、まずはDoS(サービス利用妨害)攻撃の例を示します。
操作パラメータ例: kataban=ABX'; while(1) '
上記のパラメータ操作を行うと、下記のJavaScriptが実行され無限ループとなります。
var input = 'ABX'; while(1) ''.toUpperCase();
MongoDBのタイマーにより数十秒でwhileループは中断されますが、注意が必要なのはMongoDBのJavaScriptがシングルスレッドで実行されるため、whileループの実行中はJavaScriptを使う他のクエリもブロックしてしまうことです。
この他にも、筆者が検証した環境においては、下記のパラメータを送ることでMongoDBの実行プロセスを停止させることが可能でした。
操作パラメータ例: kataban=ABX'; db.dropDatabase()//
危険なのはDoS攻撃だけではありません。データベース内のデータを不正に参照する攻撃も可能です。下記はそのための攻撃パラメータの例です。
操作パラメータ例: kataban=ABX'; throw(db.members.findOne().toSource())//
仮に、MongoDBのエラーメッセージを応答のHTMLに出力する設定となっている場合、下記のようなエラーがHTMLに出力されます。
JS Error: uncaught exception: ({_id:{str:"512d73248ead0e881f000000"}, name:"MBSD", birthyear:"1980", interests:["music", "travel"]})
エラーメッセージ中に、membersコレクションの値を検索した結果が出力されてしまっています。
応答のHTMLにエラーメッセージを出力しない設定となっている場合や、データベースのスキーマ構造を攻撃者が知らない状況もあります。RDBにおいては、このような状況であってもデータを抜き取ることができる「ブラインドSQLインジェクション」と呼ばれる手法が存在しますが、MongoDBについても同様にデータを抜き取る方法が存在します。詳細は本記事では割愛しますので、興味のある方は、以下のリンクを参照ください。
Server-Side JavaScript Injection - Bryan Sullivan (Adobe)
https://media.blackhat.com/bh-us-11/Sullivan/BH_US_11_Sullivan_Server_Side_WP.pdf
データの窃取以外には、挿入したJavaScriptを通じてサーバ上でOSコマンドを実行したり、サーバ上のファイルにアクセスしたりする攻撃が考えられます。しかし、クエリのJavaScriptからは、この種の攻撃で使われ得る危険なJavaScript関数にアクセスできないようにされているようです。詳細な情報を見つけることはできませんでしたが、MongoDB側のリスク低減策だと思われます。
上記のプログラムの問題は、入力パラメータを用いて動的にJavaScriptコードを生成している点にあります。対策は、値をJavaScript文字列としてエスケープするか、もしくはプログラムから何らかの値をJavaScriptに渡す際にscopeと呼ばれる機構を使用することにより、動的なJavaScriptコードの生成をしないようにすることです。
scopeを使うプログラムの例を下記に示します。
// JavaScriptコード自体は静的に定義 $query = <<<JSQUERY function() { return this.kataban.indexOf(input.toUpperCase()) >= 0; } JSQUERY; // 連想配列形式のscopeを作成 $scope = array('input' => (string) $_GET['kataban']); // JavaScriptコードとscopeを指定してMongoCodeオブジェクトを作成 $code = new MongoCode($query, $scope); // $whereの値にMongoCodeオブジェクトを指定してクエリを実行 $cursor = $db->products->find(array('$where' => $code));
上記のプログラムを実行すると、scopeによって「input」という名前の変数がJavaScriptに渡されます。「input」変数の値は外部からのkatabanパラメータの値です。
scopeによってJavaScriptコードを静的にすることができ、また受け渡し時に値がJavaScriptコードとして解釈・実行されることがないため、安全な値の受け渡し処理に使用できます。
もちろん、JavaScriptコード側では受け取った値を安全に使用しなければなりません。極端な例ですが、受け取った値をeval()してしまうようなJavaScriptコードは、scopeを使っていたとしても脆弱です。またscopeは、受け渡す値が配列/連想配列ならば、それを配列/連想配列のままJavaScriptに渡します。そのような値を意図しない場合には、JavaScriptかPHPコード内で型変換/型チェックを実施する必要があります。
なおJavaScriptが動作し得るのは「$where」演算子だけではありません。下記に示すMongoDBのマニュアルには、JavaScriptが動作し得る関数・構文として下記が挙げられています。
db.eval()、mapReduce、group
これらを使用する場合は、不正なJavaScriptが実行されることがないよう注意が必要です。
FAQ: MongoDB for Application Developers - MongoDB Manual
http://docs.mongodb.org/manual/faq/developers/#how-does-mongodb-address-sql-or-query-injection
SSJIについては脆弱なアプリケーションの例をもう1つ挙げます。
下記のPHPプログラムは、コミュニティサイトの会員検索プログラムの一部です。
// 検索フォームを出力 echo <<<SEARCH_FORM <form> 地域: <select name="search[region]"> <option>北海道</option> <option>東北</option> </select> 趣味: <select name="search[hobby]"> <option>スポーツ</option> <option>読書</option> </select> <input type="submit" value="検索"> </form> SEARCH_FORM; // 検索処理 $search_condition = @$_GET['search']; if ($search_condition) { $cursor = $db->members->find($search_condition);
検索フォームで指定された情報を使って会員データを検索しています。正常なケースでは、検索ボタンを押下した際に下記のようなパラメータがサーバに送信されます。
正常パラメータ例: search[region]=北海道&search[hobby]=読書
この時、プログラム中の$_GET['search']の値は下記のような連想配列となります。
array('region' => '北海道', 'hobby' => '読書')
正常なケースでは、上記のプログラムのように、この値をそのままMongoDBのfindメソッドに渡してやるだけで検索処理を実行できることになります。このような実装には、プログラムの記述が簡潔になり、また入力項目の変更時もHTMLの変更だけで済ませられる(プログラムやデータベースのスキーマを変更しなくてよい)メリットがあります。しかし、残念ながらこのような方式には問題があります。
例えば、下記のようにパラメータを操作するとどうなるでしょうか。
操作パラメータ例: search[$where]=function(){...任意のJavaScriptコード...}
プログラム内の$search_conditionの値は下記となります。
$search_condition = array('$where' => 'function(){...}');
先ほどの攻撃例のように「$where」演算子によって、パラメータ値がサーバ側でJavaScriptとして実行されてしまいます。前の例と同じく、攻撃者はDoSやデータの窃取が可能です。
また、このプログラムにはJavaScriptコードの実行以外にも問題があります。それは、検索条件を指定するパラメータを追加することにより、本来の検索条件(地域と趣味)以外の項目による検索も可能になってしまうことです。
例えば、membersコレクションに、生年月日、メールアドレスなどが含まれているとします。そしてそれらの情報は検索結果画面には表示されない「非公開」扱いの情報であるとしましょう。このような状況であっても、該当するパラメータを追加して検索を行うことで間接的にそれらの値を探り当てることができてしまいます。
例えば、下記のパラメータを追加します。
追加パラメータ例: search[birthyear][$gte]=1980
「$gte」はMongoDBの「Greater Than or Equal to」演算子です。このパラメータを追加することにより、検索結果は生年が'1980'以上の会員に絞られます。値を変えながら繰り返し検索を行いその結果を調べることで、攻撃者は他人の生年を知ることができてしまうでしょう。
MongoDBはスキーマレスという長所を持つデータベースですが、サーバ側のアプリケーション処理までそれに倣ってしまうのはよくありません。クエリに与えるデータの構造はアプリケーション側で厳密にチェックしてやりましょう。具体的な対策プログラムの例を下記に示します。
// 検索条件として受け付けるパラメータのホワイトリスト $allowed_items = array('region', 'hobby'); $search_condition = array(); // ホワイトリストを基に検索条件の連想配列を作成 foreach ($allowed_items as $i) { $search_condition[$i] = (string) $_GET['search'][$i]; }
またアプリケーション全体でMongoDBのJavaScriptを使用しないならば、MongoDBの設定でJavaScriptを無効にすることで無用なリスクをなくすことができます。設定方法については、下記のリンクを参照してください。
Configuration File Options - MongoDB Manua
http://docs.mongodb.org/manual/reference/configuration-options/#noscripting
ここまで、MongoDBを例に、NoSQLを利用したアプリケーションの2つの脆弱性について、メカニズムと対策を解説しました。次回は、「JSON文字列へのインジェクション」と「パラメータの追加」という残る2つの脆弱性について解説を加えます。
プロフェッショナルサービス事業部
シニアセキュリティスペシャリスト
ポータル・サイトなどでのWebアプリケーションのシステム開発・運用経験を生かし、セキュリティコンサルタントとして、Webアプリケーション・スマートフォンアプリケーションなどのセキュリティ診断や関連する研究開発に従事している。ECサイトや金融機関などのWebサイト検査の実績を持つ。CISSP、情報セキュリティアドミニストレータ。
Copyright © ITmedia, Inc. All Rights Reserved.