2014
06
24
06
24
MongoDB: インジェクションを考える
データベースを扱う上で付いて回るのがインジェクションというセキュリティの問題である。データベースにあるデータ(些末なものからIDやパスワードまで)を見境なくぶっこぬかれたり、認証に使っている場合は認証を偽装されたりと対策をしない理由はない。MongoDBを使う場合、どういうことを気にすればいいかを@ITの記事を参考に検証する。このブログではPythonを主に扱っているので、PyMongoでMongoDBへ接続するときの諸々を検証する。
まず前提としてアプリケーションでデータベースを使いたければ、信頼できるところが作っているドライバを使うこと。PythonとMongoDBの組み合わせならPyMongoがある。MongoDBはJavaScriptを書くことでデータの挿入、更新、削除などができる。PyMongoはこれをPythonでできるようにするものだが、基本的な部分ではJavaScriptとPythonの文法の差は現れない。JavaScriptで書いていたものをほぼそのままPythonで書くだけで使える。
下準備としてMongoDBを立ち上げ、PyMongoを使って接続する。インジェクションで認証偽装を試みるので、sessionコレクションに、インジェクションが成功したらドキュメントが得られるように一件挿入しておく。
とりあえず下記のコードを実行してみる。sessionコレクションから、sessionキーが文字列”x”のドキュメントを探し、件数を返す命令だ。
sessionキーが文字列”x”のドキュメントはないから、インタープリタには0が表示される。この条件では該当ドキュメントがsessionコレクションにはないから、認証はとおらないということ。で、正規の方法で認証をとおしたければ正規のキーを入れればいい。
1.演算子のインジェクション
参考:http://www.atmarkit.co.jp/ait/articles/1305/23/news004.html
”PHPなどの言語には、配列/連想配列と解釈できる形式で書かれたリクエストパラメータを、プログラム内で配列/連想配列に展開する機能があります”
Pythonの基本機能にはそんなことをするものはない。なのでそういう機能を暗黙のうちに提供する可能性があるのはフレームワークのほうなので、フレームワークがクライアントからPOSTされたりしたパラメータを暗黙のうちに文字列から辞書型などに変換して返すような仕様がないか確認しておく。例えばwebapp2の場合、以下のように書いておけばOK.
2.クライアントから送られたJSONをそのまま辞書型にして使うのは避ける
問題ない使いかた。データベースから文字列”session_id_xxx”に一致するセッションIDを持つドキュメントを探す。
json.loadsを使えば、JSON形式で書かれた文字列から辞書型を作れる。使いかたによっては手っ取り早くなる。
だけど上記のように書くのはまずい。悪用されると、ドキュメント検索の条件を様々に変えることができるようになり、インジェクションのきっかけに。下記では非等価を示す演算子を使って、文字列”x”に一致しないセッションIDを持つドキュメントを検索するようになる。
場合によっては変数部分はどの型が来るか管理する。クライアントから送られたJSONを辞書型に変換してそのまま使わない。
3.サーバサイドでJavaScriptを使わないようにする
参考:http://www.atmarkit.co.jp/ait/articles/1305/23/news004_2.html
MongoDBではJavaScriptが使える。PyMongoから使う場合でも、JavaScriptコードを埋め込むことができる。だがそれは任意のコードを埋め込むチャンスになるので使用を避ける。たとえば参考記事ではパラメータを大文字化して部分一致検索をかけているが、それはPyMongoでJavaScriptを埋め込まなくても可能。
JavaScriptの実行を不許可にしておくのも手。
http://docs.mongodb.org/manual/reference/configuration-options/#security.javascriptEnabled
SQLでは文の構成に文字列置換を使って問題になることがある。開発者が書いた文を手玉にとりつつ、勝手なSQL文を書かせてしまうなど。
SELECT * FROM users WHERE name = '(入力値)';
↓
SELECT * FROM users WHERE name = 't' OR 't' = 't';
上記はWikipediaから部分的に抜粋した。
PyMongoを使って適切に書けば上記のようなことは起こりづらい。
db.users.find({"name":input_value})
ただやっぱり油断していると付け入るところはあるようで、evalやexecはそもそも使わないものとして、json.loadsもちょっと使用を差し控えたほうが無難だと考えられる。使いたければ型チェックなどの厳重に設計されたバリデーションを経ること。あとデフォルトではJavaScriptが動くようになっているので、それは切ってしまうのが手っ取り早い。
まず前提としてアプリケーションでデータベースを使いたければ、信頼できるところが作っているドライバを使うこと。PythonとMongoDBの組み合わせならPyMongoがある。MongoDBはJavaScriptを書くことでデータの挿入、更新、削除などができる。PyMongoはこれをPythonでできるようにするものだが、基本的な部分ではJavaScriptとPythonの文法の差は現れない。JavaScriptで書いていたものをほぼそのままPythonで書くだけで使える。
下準備としてMongoDBを立ち上げ、PyMongoを使って接続する。インジェクションで認証偽装を試みるので、sessionコレクションに、インジェクションが成功したらドキュメントが得られるように一件挿入しておく。
import pymongo
db = pymongo.MongoClient("localhost", 27017).test_db
if not db.session.find_one():
db.session.insert({"session":"session_id_str"})
とりあえず下記のコードを実行してみる。sessionコレクションから、sessionキーが文字列”x”のドキュメントを探し、件数を返す命令だ。
print db.session.find({"session":"x"}).count()
sessionキーが文字列”x”のドキュメントはないから、インタープリタには0が表示される。この条件では該当ドキュメントがsessionコレクションにはないから、認証はとおらないということ。で、正規の方法で認証をとおしたければ正規のキーを入れればいい。
print db.session.find({"session":"session_id_str"}).count()
1.演算子のインジェクション
参考:http://www.atmarkit.co.jp/ait/articles/1305/23/news004.html
”PHPなどの言語には、配列/連想配列と解釈できる形式で書かれたリクエストパラメータを、プログラム内で配列/連想配列に展開する機能があります”
Pythonの基本機能にはそんなことをするものはない。なのでそういう機能を暗黙のうちに提供する可能性があるのはフレームワークのほうなので、フレームワークがクライアントからPOSTされたりしたパラメータを暗黙のうちに文字列から辞書型などに変換して返すような仕様がないか確認しておく。例えばwebapp2の場合、以下のように書いておけばOK.
s_id = self.request.get("s_id")
db.session.find({"session":s_id})
2.クライアントから送られたJSONをそのまま辞書型にして使うのは避ける
問題ない使いかた。データベースから文字列”session_id_xxx”に一致するセッションIDを持つドキュメントを探す。
s_id = "x"
print db.session.find({"session":s_id}).count()
json.loadsを使えば、JSON形式で書かれた文字列から辞書型を作れる。使いかたによっては手っ取り早くなる。
json_str = """{"session":"x"}""" # クライアントからJSON形式で左記のデータが送られてきたとする
obj = json.loads(json_str)
print db.session.find(obj).count()
だけど上記のように書くのはまずい。悪用されると、ドキュメント検索の条件を様々に変えることができるようになり、インジェクションのきっかけに。下記では非等価を示す演算子を使って、文字列”x”に一致しないセッションIDを持つドキュメントを検索するようになる。
injection_json = """{"session":{"$ne":"x"}}"""
obj = json.loads(injection_json)
print db.session.find(obj).count()
場合によっては変数部分はどの型が来るか管理する。クライアントから送られたJSONを辞書型に変換してそのまま使わない。
obj = json.loads(injection_json)
isinstance(obj["session"], str)
db.session.find({"session":obj["session"]})
3.サーバサイドでJavaScriptを使わないようにする
参考:http://www.atmarkit.co.jp/ait/articles/1305/23/news004_2.html
MongoDBではJavaScriptが使える。PyMongoから使う場合でも、JavaScriptコードを埋め込むことができる。だがそれは任意のコードを埋め込むチャンスになるので使用を避ける。たとえば参考記事ではパラメータを大文字化して部分一致検索をかけているが、それはPyMongoでJavaScriptを埋め込まなくても可能。
foo = {"$regex":"foo".upper()}
print db.session.find({"foo":foo}).count()
JavaScriptの実行を不許可にしておくのも手。
http://docs.mongodb.org/manual/reference/configuration-options/#security.javascriptEnabled
SQLでは文の構成に文字列置換を使って問題になることがある。開発者が書いた文を手玉にとりつつ、勝手なSQL文を書かせてしまうなど。
SELECT * FROM users WHERE name = '(入力値)';
↓
SELECT * FROM users WHERE name = 't' OR 't' = 't';
上記はWikipediaから部分的に抜粋した。
PyMongoを使って適切に書けば上記のようなことは起こりづらい。
db.users.find({"name":input_value})
ただやっぱり油断していると付け入るところはあるようで、evalやexecはそもそも使わないものとして、json.loadsもちょっと使用を差し控えたほうが無難だと考えられる。使いたければ型チェックなどの厳重に設計されたバリデーションを経ること。あとデフォルトではJavaScriptが動くようになっているので、それは切ってしまうのが手っ取り早い。
スポンサーサイト