{"title":"NoSQL Injection via PyMongo Query Operator Injection","language":"Python","severity":"High","cwe":"CWE-943","source_lines":[10,11],"flow_lines":[10,12,13],"sink_lines":[13],"vulnerable_code":"from flask import Flask, request, jsonify\nfrom pymongo import MongoClient\n\napp = Flask(__name__)\ndb = MongoClient('mongodb://localhost:27017/').iot_devices\n\n@app.route('/api/device/telemetry', methods=['POST'])\ndef fetch_device_telemetry():\n    device_id = request.json.get('device_id')\n    sensor_type = request.json.get('sensor_type')\n    query_filter = {'device_id': device_id, 'sensor_type': sensor_type}\n    telemetry_data = db.telemetry.find(query_filter)\n    results = [doc for doc in telemetry_data]\n    return jsonify({'status': 'success', 'data': results})","explanation":"The application accepts user-controlled JSON input for device_id and sensor_type parameters and directly inserts them into a MongoDB query filter without validation or sanitization. An attacker can inject MongoDB query operators (e.g., {'$ne': null}) to bypass intended query logic and access unauthorized telemetry data from other devices.","remediation":"The fix adds a sanitize_input function that validates each user-supplied parameter is strictly a string type, rejecting any dictionaries (which could contain MongoDB query operators like $ne, $gt, or $regex). If a non-string value is provided, the endpoint returns a 400 error response, preventing operator injection into the MongoDB query filter.","secure_code":"from flask import Flask, request, jsonify\nfrom pymongo import MongoClient\n\napp = Flask(__name__)\ndb = MongoClient('mongodb://localhost:27017/').iot_devices\n\ndef sanitize_input(value):\n    \"\"\"Ensure the input is a plain string value, not a query operator dict.\"\"\"\n    if not isinstance(value, str):\n        raise ValueError(\"Input must be a string\")\n    return value\n\n@app.route('/api/device/telemetry', methods=['POST'])\ndef fetch_device_telemetry():\n    try:\n        device_id = sanitize_input(request.json.get('device_id'))\n        sensor_type = sanitize_input(request.json.get('sensor_type'))\n    except (ValueError, TypeError):\n        return jsonify({'status': 'error', 'message': 'Invalid input: device_id and sensor_type must be non-empty strings'}), 400\n\n    query_filter = {'device_id': device_id, 'sensor_type': sensor_type}\n    telemetry_data = db.telemetry.find(query_filter)\n    results = [doc for doc in telemetry_data]\n    return jsonify({'status': 'success', 'data': results})"}