MongoDBのセキュリティを考える(続き)

MongoDBのセキュリティを考える
 前回にMongoDBのセキュリティを考えた。Pythonで、どういうことが起こるとまずいかを書いた。今回はPythonの軽量WebフレームワークFlaskでそのまずい状況が起こるかを調べた。コードを含めてまとめておく。


 起こるとまずいのは、JSONとなる辞書型のオブジェクト中のkey,valueのうちのvalueのほうに、文字列や数値での条件ではなく、式で条件が与えられてしまうこと。下記で示すような変数some_valueに文字列か数値が入れば、それは通常の一致検索に使われる。
db.members.find({"id":some_value})

some_valueに辞書型で{"$ne":""}という形で値を入れられると、SQLInjectionでいうところの" OR 't' = 't' "のごとく、多数のレコードをひっかけるような条件となってしまう。

 まずいことがFlaskで起こるかを調べるため、WebアプリケーションとしてのFlaskを使ったコードを用意した。
from flask import Flask

from flask import request

app = Flask(__name__)

form = """<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
</head>
<body>
<form action="/" method="POST">
ID: <input type="text" name="user" />
PW: <input type="password" name="pw" />
<input type="submit" value="send" />
</form>
</body>
</html>
"""

@app.route("/", methods=['GET', 'POST'])
def hello():
if request.method == 'GET':
return form
elif request.method == 'POST':
user = request.form.get("user")
print(request.form)
is_str = str(isinstance(user, str))
print(user)
return is_str

if __name__ == "__main__":
app.run()

上記を書いたファイルをsrv.pyとしてPythonで実行する。
python svr.py


 認証フォームを想定したもので、ルート(http://localhost:5000/)にアクセスするとID、パスワードを入れるinputが表示される。ここで入れたIDが、さきほどの辞書型の{"id":some_value}でいうところのsome_valueの位置に入る。前回でクライアントから送信された値が、サーバ側で辞書型として得られるとまずいということはわかっているので、今回はDBに問い合わせはしない。値が辞書型で得られてしまうことがあるかを確かめる。
 FlaskでPOSTされた値は、request.form.get("key_name")でえられる。key_nameに"user"と入れれば、フォームに入れた値が文字列型で得られる。これが辞書型になってしまうようなケースがあるだろうか。
 PHPやNodejsではここらでまずいことができる。だからPythonではどうだろうと試してみた。PHPやNodejsで問題が起こるのは、inputのnameが"user"から"user[$ne]"に書き換えられたとき。そのときにサーバサイドでkey_nameの値がまずい形で取り出されてしまう。ブラウザの開発者ツールで”user[$ne]"をセットしてPOSTし、サーバサイドでその値を取り出そうとしてみる。
request.form.get("user")

取り出せなーい。なぜ?print(request.form)してみればわかる。
ImmutableMultiDict([('user[$ne]', 'foo'), ('pw', 'var')])

"user"というキーのフォーム要素はなかったということ。これなら少なくとも{"id":some_value}のsome_valueの値として、条件式となる辞書型の値となることはない。

 PHPではまずいことが起きうる。それが@ITで開設されている。
「JSON文字列へのインジェクション」と「パラメータの追加」
 PHPは変数を、配列として宣言するという手順を経ずにいきなり配列として使える。かなりゆるい。それがMongoDBと組み合わせることで穴となりうる。Pythonでは起こらなかったことを考えると、MongoDBのみに非がある脆弱性とはいいがたい。

 Flaskでなら、問題はそうそう起きなさそうである。さらに一般化していえば、検索条件に使うJSONに条件として値を入れるとき、型を確認してあればInjectionのようなことはできない。SQLでいうところのプレースホルダがないのもここらが理由だろう。
comment: 0

Bash on Ubuntu on WindowsでpyenvでFlask

'17/2/16追記*********************************************
公式ドキュメント見たらpyenvがdeprecatedだったので、この記事は完全に参考にならない記事に。
https://docs.python.org/3/library/venv.html

*********************************************

 FlaskでうっすいWebアプリを突貫で作りたかった。デプロイはコンテナでするとして、適当な環境で動きを確認するまでコンテナに入れなくていい。まずてっとりばやくFlaskを動く環境を作りたかった、Pythonは最新の3.6で。なのにWindowsでFlaskがうごかない・・・Bash on Ubuntu on WIndowsがあるじゃない。でもUbuntuのPythonは3.6じゃない。そこでpyenvを使うことにした。ちなみにPythonを始める人はこういう仮想環境構築はせんでいいと思う(参考)。


 まずBashを開く。そして依存ツールのインストール。
$ sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \

libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils


 そしてpyenvのインストール。
$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv

$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc


 シェル再起動。
$ exec $SHELL


 今回使いたいPython3.6.0を入れる。
$ pyenv install 3.6.0


 カレントディレクトリで実行されるPythonを3.6.0にする。
$ pyenv local 3.6.0


 あとはpipでflaskを入れて動作確認。

comment: 0