NoSQLインジェクション

ユーザー入力によってNoSQLクエリの構造や演算子が変わらないようにしてください。

説明

信頼できない入力がクエリの構造や演算子を変更できると、NoSQLインジェクションが発生します。攻撃者はクエリを操作して、意図しないデータにアクセスする可能性があります。影響は、データベースの操作とアカウントの権限によって異なります。

想定される影響

  • 情報漏えい: 攻撃者が機密データを閲覧したり持ち出したりする可能性があります。
  • データの改ざん: 攻撃者がデータを変更・削除する可能性があります。
  • サービスの中断: 悪意のあるクエリによって、データベースの性能低下やサービスの中断が起きるおそれがあります。

対処方法

  • 入力の型、長さ、許可する値を検証してください。ユーザー名が必要なら、文字列だけを受け付けてください。
  • クエリのフィールド、演算子、返すフィールドはサーバーで決め、ユーザー入力はその値としてだけ渡してください。任意のJSONオブジェクトをクエリとして受け取らないでください。

例

Flask

変更前

python
# Flaskでの危険なNoSQLクエリ
import json
from flask import Flask, request
from pymongo import MongoClient

app = Flask(__name__)
client = MongoClient('mongodb://localhost:27017/')
db = client.mydatabase

@app.route('/user')
def get_user():
    username = request.args.get('username')
    query = json.loads(username)
    return db.users.find_one(query)

変更後

python
# FlaskでのNoSQLクエリの対策例
import re
from flask import Flask, request, abort, jsonify
from pymongo import MongoClient

app = Flask(__name__)
client = MongoClient('mongodb://localhost:27017/')
db = client.mydatabase

@app.route('/user')
def get_user():
    username = request.args.get('username', '')
    if not re.fullmatch(r'[A-Za-z0-9_.-]{1,64}', username):
        abort(400, description='Invalid username')

    # フィールドと演算子はサーバーが固定し、入力はスカラー値としてだけ使用
    user = db.users.find_one(
        {'username': username},
        {'_id': 0, 'username': 1, 'display_name': 1},
    )
    if user is None:
        abort(404)
    return jsonify(user)

説明:

  • 変更前: ユーザーが送ったJSONオブジェクトを検索条件としてそのまま使うため、クエリの構造や演算子を操作できます。
  • 変更後: 長さと文字種を制限したスカラーのユーザー名だけを受け付けます。クエリのフィールド、演算子、projectionをサーバーが固定するため、$neや$whereなどの演算子を注入できません。_idを除いたドキュメントをjsonifyでシリアライズします。

参考資料