{"title":"Code Injection via eval() on User-Controlled Expression","language":"Python","severity":"Critical","cwe":"CWE-95","source_lines":[1],"flow_lines":[1,4],"sink_lines":[4],"vulnerable_code":"def apply_iot_device_threshold(sensor_id, threshold_expr):\n    device_config = {'sensor': sensor_id, 'max_temp': 85, 'min_temp': 15}\n    try:\n        computed_threshold = eval(threshold_expr, {'__builtins__': {}}, device_config)\n        if computed_threshold > 0:\n            update_device_registry(sensor_id, computed_threshold)\n            return {'status': 'threshold_applied', 'value': computed_threshold}\n        return {'status': 'invalid_threshold', 'value': None}\n    except Exception as err:\n        return {'status': 'error', 'message': str(err)}","explanation":"The function accepts user-controlled input 'threshold_expr' and passes it directly to eval() on line 4. Despite attempting to restrict builtins, this protection is bypassable through various Python techniques such as attribute access chains (e.g., ().__class__.__bases__[0].__subclasses__()), allowing arbitrary code execution.","remediation":"Replaced the dangerous eval() call with a safe AST-based expression evaluator that only allows numeric constants, predefined variables (max_temp, min_temp), and basic arithmetic operators. The safe_eval_expr function parses the expression into an AST and recursively evaluates only whitelisted node types, preventing any code injection or arbitrary function calls.","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.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\n\ndef safe_eval_expr(expr, variables):\n    \"\"\"Safely evaluate a mathematical expression with allowed variables.\"\"\"\n    tree = ast.parse(expr, mode='eval')\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.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    else:\n        raise ValueError(f\"Unsupported expression type: {type(node).__name__}\")\n\n\ndef apply_iot_device_threshold(sensor_id, threshold_expr):\n    device_config = {'sensor': sensor_id, 'max_temp': 85, 'min_temp': 15}\n    allowed_variables = {'max_temp': device_config['max_temp'], 'min_temp': device_config['min_temp']}\n    try:\n        computed_threshold = safe_eval_expr(threshold_expr, allowed_variables)\n        if computed_threshold > 0:\n            update_device_registry(sensor_id, computed_threshold)\n            return {'status': 'threshold_applied', 'value': computed_threshold}\n        return {'status': 'invalid_threshold', 'value': None}\n    except Exception as err:\n        return {'status': 'error', 'message': str(err)}"}