{"title":"Code Injection via eval() on Untrusted Input","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[3],"flow_lines":[3,9],"sink_lines":[9],"vulnerable_code":"@app.route('/api/iot/device/metrics', methods=['POST'])\ndef calculate_device_metrics():\n    device_id = request.json.get('device_id')\n    metric_formula = request.json.get('formula')\n    sensor_data = fetch_sensor_readings(device_id)\n    temp = sensor_data['temperature']\n    humidity = sensor_data['humidity']\n    pressure = sensor_data['pressure']\n    try:\n        computed_value = eval(metric_formula)\n        log_metric(device_id, computed_value)\n        return jsonify({'device': device_id, 'result': computed_value, 'status': 'computed'})\n    except Exception as e:\n        return jsonify({'error': 'Invalid formula syntax'}), 400","explanation":"The application accepts a user-controlled 'formula' parameter from the JSON request and directly passes it to the eval() function without any validation or sanitization. This allows an attacker to execute arbitrary Python code on the server, leading to complete system compromise.","remediation":"The fix replaces the dangerous eval() call with a custom AST-based safe expression evaluator that only allows arithmetic operations, numeric constants, predefined variable names (temp, humidity, pressure), and a whitelist of safe mathematical functions. This prevents arbitrary code execution while still allowing engineers to define useful mathematical formulas for computing device metrics.","secure_code":"import ast\nimport operator\n\nALLOWED_OPERATORS = {\n    ast.Add: operator.add,\n    ast.Sub: operator.sub,\n    ast.Mult: operator.mul,\n    ast.Div: operator.truediv,\n    ast.Pow: operator.pow,\n    ast.USub: operator.neg,\n    ast.UAdd: operator.pos,\n    ast.Mod: operator.mod,\n}\n\nALLOWED_FUNCTIONS = {\n    'abs': abs,\n    'min': min,\n    'max': max,\n    'round': round,\n}\n\n\ndef safe_eval_formula(formula, variables):\n    \"\"\"Safely evaluate a mathematical formula with given variables.\"\"\"\n    try:\n        tree = ast.parse(formula, mode='eval')\n    except SyntaxError:\n        raise ValueError('Invalid formula syntax')\n\n    return _eval_node(tree.body, variables)\n\n\ndef _eval_node(node, variables):\n    if isinstance(node, ast.Expression):\n        return _eval_node(node.body, variables)\n    elif isinstance(node, ast.Constant):\n        if isinstance(node.value, (int, float)):\n            return node.value\n        raise ValueError(f'Unsupported constant type: {type(node.value).__name__}')\n    elif isinstance(node, ast.Name):\n        if node.id in variables:\n            return variables[node.id]\n        raise ValueError(f'Unknown variable: {node.id}')\n    elif isinstance(node, ast.BinOp):\n        op_type = type(node.op)\n        if op_type not in ALLOWED_OPERATORS:\n            raise ValueError(f'Unsupported operator: {op_type.__name__}')\n        left = _eval_node(node.left, variables)\n        right = _eval_node(node.right, variables)\n        if op_type == ast.Div and right == 0:\n            raise ValueError('Division by zero')\n        if op_type == ast.Pow and right > 100:\n            raise ValueError('Exponent too large')\n        return ALLOWED_OPERATORS[op_type](left, right)\n    elif isinstance(node, ast.UnaryOp):\n        op_type = type(node.op)\n        if op_type not in ALLOWED_OPERATORS:\n            raise ValueError(f'Unsupported unary operator: {op_type.__name__}')\n        operand = _eval_node(node.operand, variables)\n        return ALLOWED_OPERATORS[op_type](operand)\n    elif isinstance(node, ast.Call):\n        if isinstance(node.func, ast.Name) and node.func.id in ALLOWED_FUNCTIONS:\n            args = [_eval_node(arg, variables) for arg in node.args]\n            if node.keywords:\n                raise ValueError('Keyword arguments not supported')\n            return ALLOWED_FUNCTIONS[node.func.id](*args)\n        raise ValueError(f'Unsupported function call')\n    else:\n        raise ValueError(f'Unsupported expression type: {type(node).__name__}')\n\n\n@app.route('/api/iot/device/metrics', methods=['POST'])\ndef calculate_device_metrics():\n    device_id = request.json.get('device_id')\n    metric_formula = request.json.get('formula')\n    if not metric_formula or not isinstance(metric_formula, str):\n        return jsonify({'error': 'Formula is required and must be a string'}), 400\n    if len(metric_formula) > 500:\n        return jsonify({'error': 'Formula too long'}), 400\n    sensor_data = fetch_sensor_readings(device_id)\n    variables = {\n        'temp': sensor_data['temperature'],\n        'humidity': sensor_data['humidity'],\n        'pressure': sensor_data['pressure'],\n    }\n    try:\n        computed_value = safe_eval_formula(metric_formula, variables)\n        log_metric(device_id, computed_value)\n        return jsonify({'device': device_id, 'result': computed_value, 'status': 'computed'})\n    except ValueError as e:\n        return jsonify({'error': str(e)}), 400\n    except Exception as e:\n        return jsonify({'error': 'Invalid formula syntax'}), 400"}