{"title":"NoSQL Injection via Untrusted MongoDB Query Operators","language":"Python","severity":"High","cwe":"CWE-943","source_lines":[10],"flow_lines":[10,11],"sink_lines":[12],"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    time_range = request.json.get('time_filter', {})\n    query = {'device_id': device_id, 'timestamp': time_range}\n    telemetry_data = list(db.telemetry.find(query))\n    return jsonify({'status': 'success', 'data': telemetry_data})","explanation":"The application accepts arbitrary JSON objects from user input for the 'time_filter' parameter and directly embeds them into a MongoDB query without validation. This allows attackers to inject MongoDB query operators like $ne, $gt, $where, or $regex to bypass authentication, extract unauthorized data, or perform denial of service attacks.","remediation":"The fix sanitizes the time_filter input by only allowing a whitelist of safe MongoDB comparison operators ($gt, $gte, $lt, $lte) and ensuring their values are primitive types (ISO format strings converted to datetime objects, or numeric timestamps). Additionally, device_id is validated to be a non-empty string, preventing operator injection through that parameter as well.","secure_code":"from flask import Flask, request, jsonify\nfrom pymongo import MongoClient\nfrom datetime import datetime\n\napp = Flask(__name__)\ndb = MongoClient('mongodb://localhost:27017/').iot_devices\n\ndef sanitize_time_filter(time_filter):\n    \"\"\"Validate and sanitize time filter to only allow safe, expected values.\"\"\"\n    if not isinstance(time_filter, dict):\n        return None\n    \n    sanitized = {}\n    allowed_operators = {'$gt', '$gte', '$lt', '$lte'}\n    \n    for key, value in time_filter.items():\n        if key not in allowed_operators:\n            continue\n        if isinstance(value, str):\n            try:\n                sanitized[key] = datetime.fromisoformat(value)\n            except (ValueError, TypeError):\n                continue\n        elif isinstance(value, (int, float)):\n            sanitized[key] = value\n    \n    return sanitized if sanitized else None\n\n@app.route('/api/device/telemetry', methods=['POST'])\ndef fetch_device_telemetry():\n    if not request.json:\n        return jsonify({'status': 'error', 'message': 'Invalid request body'}), 400\n    \n    device_id = request.json.get('device_id')\n    \n    if not isinstance(device_id, str) or not device_id.strip():\n        return jsonify({'status': 'error', 'message': 'Invalid device_id'}), 400\n    \n    time_range_input = request.json.get('time_filter', {})\n    \n    query = {'device_id': device_id}\n    \n    sanitized_time_filter = sanitize_time_filter(time_range_input)\n    if sanitized_time_filter:\n        query['timestamp'] = sanitized_time_filter\n    \n    telemetry_data = list(db.telemetry.find(query))\n    return jsonify({'status': 'success', 'data': telemetry_data})"}