# Code Injection via eval() with Untrusted Input

Language: Python
Severity: Critical
CWE: CWE-94

## Source
6

## Flow
6-8

## Sink
8

## Vulnerable Code
```python
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/iot/sensor/threshold', methods=['POST'])
def calc_alert_threshold():
    sensor_id = request.json.get('sensor_id', 'unknown')
    expr = request.json.get('threshold_expr', '0')
    try:
        result = eval(f"float({expr})")
        return jsonify({'sensor': sensor_id, 'threshold': result, 'status': 'configured'})
    except Exception as e:
        return jsonify({'error': str(e)}), 400
```

## Explanation

The `threshold_expr` value is taken directly from user-controlled JSON input on line 6 and passed unsanitized into `eval()` via an f-string on line 8. Although wrapped in `float(...)`, the attacker-controlled expression is evaluated as Python code before `float()` ever executes, allowing arbitrary code execution. The try/except block suppresses errors, making blind injection viable.

## Remediation

The fix replaces the dangerous `eval()` call with a safe AST-based expression evaluator (`safe_eval_numeric`). This function parses the user-provided expression into an Abstract Syntax Tree, then walks every node to verify it is strictly a numeric literal or basic arithmetic operator—rejecting any function calls, attribute access, name lookups, imports, or non-numeric constants. Only after full validation is the sanitized AST compiled and evaluated, making arbitrary code injection impossible.

## Secure Code
```python
from flask import Flask, request, jsonify
import ast
import operator

app = Flask(__name__)

# Safe expression evaluator that only allows basic arithmetic on numeric literals
ALLOWED_NODE_TYPES = (
    ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant,
    ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv,
    ast.Mod, ast.Pow, ast.USub, ast.UAdd,
)

def safe_eval_numeric(expr: str) -> float:
    """Safely evaluate a numeric arithmetic expression.
    
    Only allows numeric literals and basic arithmetic operators:
    +, -, *, /, //, %, **
    Raises ValueError for any disallowed constructs.
    """
    if not isinstance(expr, str) or not expr.strip():
        raise ValueError("Expression must be a non-empty string")
    
    expr = expr.strip()
    
    # Parse the expression into an AST
    try:
        tree = ast.parse(expr, mode='eval')
    except SyntaxError as e:
        raise ValueError(f"Invalid expression syntax: {e}")
    
    # Walk the AST and verify every node is an allowed type
    for node in ast.walk(tree):
        if not isinstance(node, ALLOWED_NODE_TYPES):
            raise ValueError(
                f"Disallowed expression element: {type(node).__name__}. "
                "Only numeric literals and basic arithmetic operators are permitted."
            )
        # Ensure Constant nodes are only numeric (int or float)
        if isinstance(node, ast.Constant) and not isinstance(node.value, (int, float)):
            raise ValueError(
                f"Disallowed literal type: {type(node.value).__name__}. "
                "Only numeric values are permitted."
            )
    
    # Safely compile and evaluate the validated AST
    code = compile(tree, filename='<threshold_expr>', mode='eval')
    result = eval(code)
    return float(result)


@app.route('/iot/sensor/threshold', methods=['POST'])
def calc_alert_threshold():
    sensor_id = request.json.get('sensor_id', 'unknown')
    expr = request.json.get('threshold_expr', '0')
    try:
        result = safe_eval_numeric(expr)
        return jsonify({'sensor': sensor_id, 'threshold': result, 'status': 'configured'})
    except ValueError as e:
        return jsonify({'error': str(e)}), 400
    except Exception:
        return jsonify({'error': 'Failed to evaluate threshold expression'}), 400
```
