# NoSQL Injection via PyMongo Query Operator Injection

Language: Python
Severity: High
CWE: CWE-943

## Source
10-11

## Flow
10-12-13

## Sink
13

## Vulnerable Code
```python
from flask import Flask, request, jsonify
from pymongo import MongoClient

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

@app.route('/api/device/telemetry', methods=['POST'])
def fetch_device_telemetry():
    device_id = request.json.get('device_id')
    sensor_type = request.json.get('sensor_type')
    query_filter = {'device_id': device_id, 'sensor_type': sensor_type}
    telemetry_data = db.telemetry.find(query_filter)
    results = [doc for doc in telemetry_data]
    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
```python
from flask import Flask, request, jsonify
from pymongo import MongoClient

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

def sanitize_input(value):
    """Ensure the input is a plain string value, not a query operator dict."""
    if not isinstance(value, str):
        raise ValueError("Input must be a string")
    return value

@app.route('/api/device/telemetry', methods=['POST'])
def fetch_device_telemetry():
    try:
        device_id = sanitize_input(request.json.get('device_id'))
        sensor_type = sanitize_input(request.json.get('sensor_type'))
    except (ValueError, TypeError):
        return jsonify({'status': 'error', 'message': 'Invalid input: device_id and sensor_type must be non-empty strings'}), 400

    query_filter = {'device_id': device_id, 'sensor_type': sensor_type}
    telemetry_data = db.telemetry.find(query_filter)
    results = [doc for doc in telemetry_data]
    return jsonify({'status': 'success', 'data': results})
```
