{"title":"NoSQL Injection via Untrusted MongoDB Query Operators","language":"Python","severity":"High","cwe":"CWE-943","source_lines":[10,11],"flow_lines":[10,11,12,13,14,15,16],"sink_lines":[16],"vulnerable_code":"from flask import Flask, request, jsonify\nfrom pymongo import MongoClient\n\napp = Flask(__name__)\ndb_client = MongoClient('mongodb://localhost:27017/')\niot_db = db_client['smart_home_devices']\n\n@app.route('/api/device/telemetry', methods=['POST'])\ndef fetch_device_telemetry():\n    device_params = request.get_json()\n    device_id = device_params.get('device_id')\n    time_filter = device_params.get('timestamp')\n    query_filter = {\n        'device_id': device_id,\n        'timestamp': time_filter,\n        'status': 'active'\n    }\n    telemetry_data = iot_db.telemetry_logs.find(query_filter)\n    results = []\n    for record in telemetry_data:\n        record['_id'] = str(record['_id'])\n        results.append(record)\n    return jsonify({'telemetry': results, 'count': len(results)})","explanation":"The application accepts user-controlled JSON input for 'device_id' and 'timestamp' parameters and directly passes them into a MongoDB query without validation or sanitization. An attacker can inject MongoDB query operators (like $ne, $gt, $where, $regex) through these fields to bypass intended filters, extract unauthorized data, or cause denial of service.","remediation":"The fix introduces a sanitize_input function that rejects any dict or list values, preventing MongoDB query operators (like $ne, $gt, $regex) from being injected through user-supplied fields. Additionally, the device_id and timestamp values are explicitly cast to strings to ensure only simple scalar values are used in the query, and input validation returns appropriate error responses for malformed requests.","secure_code":"from flask import Flask, request, jsonify\nfrom pymongo import MongoClient\n\napp = Flask(__name__)\ndb_client = MongoClient('mongodb://localhost:27017/')\niot_db = db_client['smart_home_devices']\n\n\ndef sanitize_input(value):\n    \"\"\"Ensure the value is a simple scalar type, not a dict or list that could contain MongoDB operators.\"\"\"\n    if isinstance(value, (dict, list)):\n        return None\n    return value\n\n\n@app.route('/api/device/telemetry', methods=['POST'])\ndef fetch_device_telemetry():\n    device_params = request.get_json()\n    if not device_params or not isinstance(device_params, dict):\n        return jsonify({'error': 'Invalid request body'}), 400\n\n    device_id = sanitize_input(device_params.get('device_id'))\n    time_filter = sanitize_input(device_params.get('timestamp'))\n\n    if device_id is None:\n        return jsonify({'error': 'Invalid or missing device_id'}), 400\n\n    # Ensure device_id is a string\n    device_id = str(device_id)\n\n    query_filter = {\n        'device_id': device_id,\n        'status': 'active'\n    }\n\n    # Only add timestamp filter if a valid scalar value was provided\n    if time_filter is not None:\n        query_filter['timestamp'] = str(time_filter)\n\n    telemetry_data = iot_db.telemetry_logs.find(query_filter)\n    results = []\n    for record in telemetry_data:\n        record['_id'] = str(record['_id'])\n        results.append(record)\n    return jsonify({'telemetry': results, 'count': len(results)})"}