# NoSQL Injection via MongoDB Operator Injection

Language: Python
Severity: High
CWE: CWE-943

## Source
9-10

## Flow
9-11-12

## Sink
12

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

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

@app.route('/api/devices/telemetry', methods=['POST'])
def fetch_device_telemetry():
    device_id = request.json.get('device_id')
    threshold = request.json.get('threshold')
    query = {'device_id': device_id, 'temperature': threshold}
    telemetry_data = db.sensor_readings.find(query)
    return jsonify([doc for doc in telemetry_data])
```

## Explanation

The code directly passes user-controlled JSON input (device_id and threshold) into a MongoDB query without validation or sanitization. An attacker can inject MongoDB operators like {'$gt': 0} or {'$ne': null} through the threshold parameter to bypass intended query logic and extract unauthorized data.

## Remediation

The fix validates that device_id is strictly a string and threshold is strictly a numeric value (int or float), rejecting any dict or list inputs that could contain MongoDB operators like $gt or $ne. This prevents NoSQL operator injection by ensuring only primitive, expected types are passed into the query. Additionally, the threshold is now used in an explicit $lte operator to clarify the intended query semantics.

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

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

@app.route('/api/devices/telemetry', methods=['POST'])
def fetch_device_telemetry():
    device_id = request.json.get('device_id')
    threshold = request.json.get('threshold')

    # Validate device_id is a plain string
    if not isinstance(device_id, str):
        return jsonify({'error': 'device_id must be a string'}), 400

    # Validate threshold is a numeric value (int or float), not a dict/list
    if not isinstance(threshold, (int, float)):
        return jsonify({'error': 'threshold must be a numeric value'}), 400

    query = {'device_id': device_id, 'temperature': {'$lte': threshold}}
    telemetry_data = list(db.sensor_readings.find(query, {'_id': 0}))
    return jsonify(telemetry_data)
```
