# Code Injection via eval() on Untrusted User Input

Language: Python
Severity: Critical
CWE: CWE-94

## Source
4

## Flow
4-9

## Sink
9

## Vulnerable Code
```python
@app.route('/api/iot/device/metrics', methods=['POST'])
def calculate_device_metrics():
    device_id = request.json.get('device_id')
    metric_formula = request.json.get('formula')
    sensor_data = fetch_sensor_readings(device_id)
    temp = sensor_data['temperature']
    humidity = sensor_data['humidity']
    pressure = sensor_data['pressure']
    try:
        computed_value = eval(metric_formula)
        store_metric(device_id, computed_value)
        return jsonify({'device': device_id, 'result': computed_value, 'status': 'success'})
    except Exception as e:
        return jsonify({'error': 'Invalid formula syntax'}), 400
```

## Explanation

The application accepts an untrusted user-controlled 'formula' parameter via request.json.get('formula') and directly passes it to the eval() function without any validation or sanitization. This allows attackers to execute arbitrary Python code on the server by injecting malicious expressions instead of legitimate mathematical formulas.

## Remediation

The fix replaces the dangerous eval() call with a custom safe_eval_formula() function that uses Python's ast module to parse the formula into an Abstract Syntax Tree, then walks the tree and only evaluates nodes that are numeric constants, whitelisted variable names, or safe mathematical operators. This approach prevents any arbitrary code execution while still allowing legitimate mathematical expressions using sensor data variables.

## Secure Code
```python
import ast
import operator

# Safe mathematical operations whitelist
SAFE_OPERATORS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.FloorDiv: operator.floordiv,
    ast.Mod: operator.mod,
    ast.Pow: operator.pow,
    ast.USub: operator.neg,
    ast.UAdd: operator.pos,
}

def safe_eval_formula(formula, variables):
    """Safely evaluate a mathematical formula with only allowed operations and variables."""
    try:
        tree = ast.parse(formula, mode='eval')
    except SyntaxError:
        raise ValueError("Invalid formula syntax")

    def _eval_node(node):
        if isinstance(node, ast.Expression):
            return _eval_node(node.body)
        elif isinstance(node, ast.Constant):
            if isinstance(node.value, (int, float)):
                return node.value
            raise ValueError(f"Unsupported constant type: {type(node.value).__name__}")
        elif isinstance(node, ast.Name):
            if node.id in variables:
                return variables[node.id]
            raise ValueError(f"Unknown variable: {node.id}")
        elif isinstance(node, ast.BinOp):
            op_type = type(node.op)
            if op_type not in SAFE_OPERATORS:
                raise ValueError(f"Unsupported operator: {op_type.__name__}")
            left = _eval_node(node.left)
            right = _eval_node(node.right)
            if op_type == ast.Pow and right > 100:
                raise ValueError("Exponent too large")
            if op_type in (ast.Div, ast.FloorDiv, ast.Mod) and right == 0:
                raise ValueError("Division by zero")
            return SAFE_OPERATORS[op_type](left, right)
        elif isinstance(node, ast.UnaryOp):
            op_type = type(node.op)
            if op_type not in SAFE_OPERATORS:
                raise ValueError(f"Unsupported unary operator: {op_type.__name__}")
            operand = _eval_node(node.operand)
            return SAFE_OPERATORS[op_type](operand)
        else:
            raise ValueError(f"Unsupported expression type: {type(node).__name__}")

    return _eval_node(tree)


@app.route('/api/iot/device/metrics', methods=['POST'])
def calculate_device_metrics():
    device_id = request.json.get('device_id')
    metric_formula = request.json.get('formula')

    if not device_id or not metric_formula:
        return jsonify({'error': 'Missing device_id or formula'}), 400

    if len(metric_formula) > 500:
        return jsonify({'error': 'Formula too long'}), 400

    sensor_data = fetch_sensor_readings(device_id)
    variables = {
        'temp': sensor_data['temperature'],
        'humidity': sensor_data['humidity'],
        'pressure': sensor_data['pressure'],
    }

    try:
        computed_value = safe_eval_formula(metric_formula, variables)
        store_metric(device_id, computed_value)
        return jsonify({'device': device_id, 'result': computed_value, 'status': 'success'})
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception as e:
        return jsonify({'error': 'Invalid formula syntax'}), 400
```
