# NoSQL Injection via Untrusted MongoDB Query Operators

Language: Python
Severity: High
CWE: CWE-943

## Source
10

## Flow
10-11

## Sink
12

## 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')
    time_range = request.json.get('time_filter', {})
    query = {'device_id': device_id, 'timestamp': time_range}
    telemetry_data = list(db.telemetry.find(query))
    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
```python
from flask import Flask, request, jsonify
from pymongo import MongoClient
from datetime import datetime

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

def sanitize_time_filter(time_filter):
    """Validate and sanitize time filter to only allow safe, expected values."""
    if not isinstance(time_filter, dict):
        return None
    
    sanitized = {}
    allowed_operators = {'$gt', '$gte', '$lt', '$lte'}
    
    for key, value in time_filter.items():
        if key not in allowed_operators:
            continue
        if isinstance(value, str):
            try:
                sanitized[key] = datetime.fromisoformat(value)
            except (ValueError, TypeError):
                continue
        elif isinstance(value, (int, float)):
            sanitized[key] = value
    
    return sanitized if sanitized else None

@app.route('/api/device/telemetry', methods=['POST'])
def fetch_device_telemetry():
    if not request.json:
        return jsonify({'status': 'error', 'message': 'Invalid request body'}), 400
    
    device_id = request.json.get('device_id')
    
    if not isinstance(device_id, str) or not device_id.strip():
        return jsonify({'status': 'error', 'message': 'Invalid device_id'}), 400
    
    time_range_input = request.json.get('time_filter', {})
    
    query = {'device_id': device_id}
    
    sanitized_time_filter = sanitize_time_filter(time_range_input)
    if sanitized_time_filter:
        query['timestamp'] = sanitized_time_filter
    
    telemetry_data = list(db.telemetry.find(query))
    return jsonify({'status': 'success', 'data': telemetry_data})
```
