# NoSQL Injection via Untrusted MongoDB Query Operators

Language: Python
Severity: High
CWE: CWE-943

## Source
10-11

## Flow
10-11-12-13-14-15-16

## Sink
16

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

app = Flask(__name__)
db_client = MongoClient('mongodb://localhost:27017/')
iot_db = db_client['smart_home_devices']

@app.route('/api/device/telemetry', methods=['POST'])
def fetch_device_telemetry():
    device_params = request.get_json()
    device_id = device_params.get('device_id')
    time_filter = device_params.get('timestamp')
    query_filter = {
        'device_id': device_id,
        'timestamp': time_filter,
        'status': 'active'
    }
    telemetry_data = iot_db.telemetry_logs.find(query_filter)
    results = []
    for record in telemetry_data:
        record['_id'] = str(record['_id'])
        results.append(record)
    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
```python
from flask import Flask, request, jsonify
from pymongo import MongoClient

app = Flask(__name__)
db_client = MongoClient('mongodb://localhost:27017/')
iot_db = db_client['smart_home_devices']


def sanitize_input(value):
    """Ensure the value is a simple scalar type, not a dict or list that could contain MongoDB operators."""
    if isinstance(value, (dict, list)):
        return None
    return value


@app.route('/api/device/telemetry', methods=['POST'])
def fetch_device_telemetry():
    device_params = request.get_json()
    if not device_params or not isinstance(device_params, dict):
        return jsonify({'error': 'Invalid request body'}), 400

    device_id = sanitize_input(device_params.get('device_id'))
    time_filter = sanitize_input(device_params.get('timestamp'))

    if device_id is None:
        return jsonify({'error': 'Invalid or missing device_id'}), 400

    # Ensure device_id is a string
    device_id = str(device_id)

    query_filter = {
        'device_id': device_id,
        'status': 'active'
    }

    # Only add timestamp filter if a valid scalar value was provided
    if time_filter is not None:
        query_filter['timestamp'] = str(time_filter)

    telemetry_data = iot_db.telemetry_logs.find(query_filter)
    results = []
    for record in telemetry_data:
        record['_id'] = str(record['_id'])
        results.append(record)
    return jsonify({'telemetry': results, 'count': len(results)})
```
