{"title":"Arbitrary Code Execution via eval on Untrusted Expression","language":"Python","severity":"Critical","cwe":"CWE-95","source_lines":[4],"flow_lines":[4,9],"sink_lines":[9],"vulnerable_code":"@app.route('/iot/device/metrics', methods=['POST'])\ndef process_device_telemetry():\n    device_id = request.json.get('device_id')\n    metric_formula = request.json.get('aggregation_rule')\n    sensor_data = fetch_sensor_readings(device_id)\n    temp = sensor_data.get('temperature', 0)\n    humidity = sensor_data.get('humidity', 0)\n    pressure = sensor_data.get('pressure', 0)\n    computed_metric = eval(metric_formula)\n    store_computed_value(device_id, computed_metric)\n    return jsonify({'device': device_id, 'result': computed_metric})","explanation":"The application accepts a user-controlled 'aggregation_rule' parameter from the JSON request and passes it directly to the eval() function without any validation or sanitization. This allows attackers to execute arbitrary Python code on the server by injecting malicious payloads through the metric_formula variable.","remediation":"The fix replaces the dangerous eval() call with a custom safe expression evaluator built on Python's ast module. The safe_eval_formula function parses the formula into an AST and only allows numeric constants, whitelisted variable names (temp, humidity, pressure), basic arithmetic operators, and a small set of safe math functions (abs, min, max, round). Any attempt to use attribute access, imports, function calls to non-whitelisted functions, or other dangerous constructs will raise a ValueError.","secure_code":"import ast\nimport operator\n\n# Safe mathematical operations allowed in aggregation rules\nSAFE_OPERATORS = {\n    ast.Add: operator.add,\n    ast.Sub: operator.sub,\n    ast.Mult: operator.mul,\n    ast.Div: operator.truediv,\n    ast.FloorDiv: operator.floordiv,\n    ast.Mod: operator.mod,\n    ast.Pow: operator.pow,\n    ast.USub: operator.neg,\n    ast.UAdd: operator.pos,\n}\n\nSAFE_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    def _eval_node(node):\n        if isinstance(node, ast.Expression):\n            return _eval_node(node.body)\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)}\")\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 SAFE_OPERATORS:\n                raise ValueError(f\"Unsupported operator: {op_type.__name__}\")\n            left = _eval_node(node.left)\n            right = _eval_node(node.right)\n            if op_type == ast.Div and right == 0:\n                raise ValueError(\"Division by zero\")\n            return SAFE_OPERATORS[op_type](left, right)\n        elif isinstance(node, ast.UnaryOp):\n            op_type = type(node.op)\n            if op_type not in SAFE_OPERATORS:\n                raise ValueError(f\"Unsupported unary operator: {op_type.__name__}\")\n            operand = _eval_node(node.operand)\n            return SAFE_OPERATORS[op_type](operand)\n        elif isinstance(node, ast.Call):\n            if isinstance(node.func, ast.Name) and node.func.id in SAFE_FUNCTIONS:\n                args = [_eval_node(arg) for arg in node.args]\n                if node.keywords:\n                    raise ValueError(\"Keyword arguments not supported\")\n                return SAFE_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    return _eval_node(tree)\n\n\n@app.route('/iot/device/metrics', methods=['POST'])\ndef process_device_telemetry():\n    device_id = request.json.get('device_id')\n    metric_formula = request.json.get('aggregation_rule')\n    if not metric_formula or not isinstance(metric_formula, str):\n        return jsonify({'error': 'Invalid aggregation rule'}), 400\n    if len(metric_formula) > 200:\n        return jsonify({'error': 'Aggregation rule too long'}), 400\n    sensor_data = fetch_sensor_readings(device_id)\n    variables = {\n        'temp': sensor_data.get('temperature', 0),\n        'humidity': sensor_data.get('humidity', 0),\n        'pressure': sensor_data.get('pressure', 0),\n    }\n    try:\n        computed_metric = safe_eval_formula(metric_formula, variables)\n    except ValueError as e:\n        return jsonify({'error': f'Invalid formula: {str(e)}'}), 400\n    store_computed_value(device_id, computed_metric)\n    return jsonify({'device': device_id, 'result': computed_metric})"}