MongoDBのセキュリティを考える

 MongoDBをけっこう積極的に使っている。そのためにセキュリティについてもある程度考えていたが、ここで改めてそれをまとめておく。SQL Injectionみたいなのがないのかってあたりなど。


 Pythonで公式のドライバであるPymongoを使って、認証突破の手掛かりになるようなことができないか試してみる。下記SQLのようなことがMongoDBでできるかどうか。
SELECT * FROM users WHERE name = '(入力値)';

SELECT * FROM users WHERE name = 't' OR 't' = 't';

 まず準備。Pymongoをインストールした上で、MongoDBを起動して、接続する。さらに、ユーザデータとなるものを一件インサートする。
from pymongo import MongoClient


client = MongoClient()
db = client.test_database

db.members.insert({"_id":"kuma", "pw":"foo"})

 これでkumaというユーザがいる状態になった。このkumaを引っ張り出す基本的なクエリは下記の通り。
member_id = "kuma"

db.members.find_one({"_id":member_id})


 下記のクエリは該当データがないのでなにも取ってこれない。
member_id = "john doe"

db.members.find_one({"_id":member_id})


 上記を書き換えて「"john doe"がIDではない」という条件を与えてみる。下記のようになるが、これでkumaも引っ張ってこれる。
member_id = {"$ne":"john doe"}

db.members.find_one({"_id":member_id})

ユーザIDを条件として入れるべきところを、「○○ではない」という条件に書き換えてしまうのがMongoDBのクラックの仕方で最たるものだろう。

 上記はmember_idに辞書型で条件を与えているのでユーザkumaも取ってこれる。これが辞書型ではなく、きちんと文字列型で与えられるなら、当然kumaは取ってこれない。
member_id = '{"$ne":"john doe"}'

db.members.find_one({"_id":member_id})

つまり、条件を与えるところは文字列型できっちり与えられるように固めて、そこを辞書型で与えられたりしないようにしておけということになる。それを考えると、やってはいけないのはクライアント側からJSONでデータを送らせて、それをなにも考えずにJSON.loadsで辞書型データにして扱ってしまうことだ。
 例として、クライアントからJSONを送られたとして、サーバ側でそれを辞書構造のデータにパースして扱うコードを書いてみる。 
ob = json.loads('{"_id":"john doe"}')

db.members.find_one({"_id":ob["_id"]})

 これがアウトなコードの例である。JSONの_idの値はクライアント側で任意のものにできる。ということは下記のようにしてしまえば、ユーザkumaはクエリにひっかかってくる。
ob = json.loads('{"_id":{"$ne":"john doe"}}')

db.members.find_one({"_id":ob["_id"]})

 ググってみるとクライアント側でフォームデータをJSONにしたいというニーズは結構あるようだが、MongoDBを使っている場合はそれは危険。
https://www.google.co.jp/search?q=form+to+json
 もし使っているWebフレームワークが、GETで送られてきた値を、空気を読んで辞書型で返してくれるなんて危なっかしい機能があったら回避策を取ろう。PythonでMongoDBを使っている場合、条件式は辞書型で与えられる。条件の値が入る部分に文字列型で値が入るということが保証されればいい。
 PHPはGETで送られてきた値を配列型にキャストして返してくることがあるので、脆弱性が生まれる要因になっている。PHPでMongoDBを使う場合は要注意。このあたりはもはやPHPの言語仕様との相性の悪さか。


 あと覚えておくべきこととしては、MongoDBにJavaScriptを渡すと実行してくれるなんて機能もある。db.members.find("this._id == "kuma")って書けばユーザkumaを取ってこれるが、悪用も考えておくべきである。まあこのJSをちゃんとJSONになるようにカーリーブラケット"{}"で囲んでやればそれでOK。あとは心配ならサーバサイドでのJS実行を止めてしまえばいい。
https://docs.mongodb.com/manual/core/server-side-javascript/
MongoDBがJSを実行できるってのは仕様であって、それが不安になるなら選択肢は用意されている。


 MongoDBを使う場合、クエリなどに入れる値の型は最低限注意するべきことだろう。文字列が入るべきところに、別の型で条件式とか入れられたりするような設計はしてはいけない。暗黙のキャストを多用せずに、型を意識してやっていけばインジェクションみたいなものは避けられるだろうと考えている。
comment: 0