{"title":"Code Injection via eval() with Untrusted Input","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[6],"flow_lines":[6,8],"sink_lines":[8],"vulnerable_code":"from flask import Flask, request, jsonify\napp = Flask(__name__)\n@app.route('/iot/sensor/threshold', methods=['POST'])\ndef calc_alert_threshold():\n    sensor_id = request.json.get('sensor_id', 'unknown')\n    expr = request.json.get('threshold_expr', '0')\n    try:\n        result = eval(f\"float({expr})\")\n        return jsonify({'sensor': sensor_id, 'threshold': result, 'status': 'configured'})\n    except Exception as e:\n        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":"from flask import Flask, request, jsonify\nimport ast\nimport operator\n\napp = Flask(__name__)\n\n# Safe expression evaluator that only allows basic arithmetic on numeric literals\nALLOWED_NODE_TYPES = (\n    ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant,\n    ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv,\n    ast.Mod, ast.Pow, ast.USub, ast.UAdd,\n)\n\ndef safe_eval_numeric(expr: str) -> float:\n    \"\"\"Safely evaluate a numeric arithmetic expression.\n    \n    Only allows numeric literals and basic arithmetic operators:\n    +, -, *, /, //, %, **\n    Raises ValueError for any disallowed constructs.\n    \"\"\"\n    if not isinstance(expr, str) or not expr.strip():\n        raise ValueError(\"Expression must be a non-empty string\")\n    \n    expr = expr.strip()\n    \n    # Parse the expression into an AST\n    try:\n        tree = ast.parse(expr, mode='eval')\n    except SyntaxError as e:\n        raise ValueError(f\"Invalid expression syntax: {e}\")\n    \n    # Walk the AST and verify every node is an allowed type\n    for node in ast.walk(tree):\n        if not isinstance(node, ALLOWED_NODE_TYPES):\n            raise ValueError(\n                f\"Disallowed expression element: {type(node).__name__}. \"\n                \"Only numeric literals and basic arithmetic operators are permitted.\"\n            )\n        # Ensure Constant nodes are only numeric (int or float)\n        if isinstance(node, ast.Constant) and not isinstance(node.value, (int, float)):\n            raise ValueError(\n                f\"Disallowed literal type: {type(node.value).__name__}. \"\n                \"Only numeric values are permitted.\"\n            )\n    \n    # Safely compile and evaluate the validated AST\n    code = compile(tree, filename='<threshold_expr>', mode='eval')\n    result = eval(code)\n    return float(result)\n\n\n@app.route('/iot/sensor/threshold', methods=['POST'])\ndef calc_alert_threshold():\n    sensor_id = request.json.get('sensor_id', 'unknown')\n    expr = request.json.get('threshold_expr', '0')\n    try:\n        result = safe_eval_numeric(expr)\n        return jsonify({'sensor': sensor_id, 'threshold': result, 'status': 'configured'})\n    except ValueError as e:\n        return jsonify({'error': str(e)}), 400\n    except Exception:\n        return jsonify({'error': 'Failed to evaluate threshold expression'}), 400"}