{"title":"Code Injection via exec() on Untrusted Configuration Data","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[3],"flow_lines":[3,4,8,9,10,3,4,11,12,13],"sink_lines":[10,13],"vulnerable_code":"import json\nimport os\n\ndef apply_iot_device_policy(device_id, policy_json):\n    policy_data = json.loads(policy_json)\n    device_config = {\n        'device_id': device_id,\n        'max_temp': 85,\n        'alert_threshold': 75,\n        'sampling_rate': 1000\n    }\n    if 'custom_rules' in policy_data:\n        rules_code = policy_data['custom_rules']\n        exec(rules_code, {'config': device_config, 'os': os})\n    if 'override_params' in policy_data:\n        for param, value_expr in policy_data['override_params'].items():\n            device_config[param] = eval(value_expr)\n    return device_config","explanation":"The function accepts untrusted JSON input via policy_json parameter, parses it, and directly passes user-controlled data to exec() and eval() functions without any validation or sanitization. This allows an attacker to inject arbitrary Python code through the 'custom_rules' field (executed via exec()) or 'override_params' values (executed via eval()), enabling complete system compromise.","remediation":"The fix completely removes exec() and eval() calls, replacing them with a safe declarative rule engine that only allows whitelisted parameters and numeric values. Custom rules are now structured as JSON objects with predefined actions and conditions instead of arbitrary code strings, and override parameters are validated against an allowlist with safe numeric parsing.","secure_code":"import json\n\nALLOWED_PARAMS = {'max_temp', 'alert_threshold', 'sampling_rate', 'min_temp', 'polling_interval'}\nALLOWED_RULE_ACTIONS = {'set_max_temp', 'set_alert_threshold', 'set_sampling_rate', 'set_min_temp', 'set_polling_interval'}\n\ndef validate_numeric_value(value):\n    \"\"\"Safely parse a numeric value from a string.\"\"\"\n    try:\n        if '.' in str(value):\n            return float(value)\n        return int(value)\n    except (ValueError, TypeError):\n        raise ValueError(f\"Invalid numeric value: {value}\")\n\ndef apply_rule(device_config, rule):\n    \"\"\"Apply a single validated rule to device configuration.\"\"\"\n    action = rule.get('action')\n    if action not in ALLOWED_RULE_ACTIONS:\n        raise ValueError(f\"Disallowed rule action: {action}\")\n    \n    param_name = action.replace('set_', '', 1)\n    if param_name not in ALLOWED_PARAMS:\n        raise ValueError(f\"Invalid parameter in rule: {param_name}\")\n    \n    value = validate_numeric_value(rule.get('value'))\n    \n    condition = rule.get('condition')\n    if condition:\n        compare_param = condition.get('param')\n        if compare_param not in ALLOWED_PARAMS and compare_param not in device_config:\n            raise ValueError(f\"Invalid condition parameter: {compare_param}\")\n        operator = condition.get('operator')\n        threshold = validate_numeric_value(condition.get('value'))\n        current_value = device_config.get(compare_param, 0)\n        \n        if operator == 'greater_than' and not (current_value > threshold):\n            return\n        elif operator == 'less_than' and not (current_value < threshold):\n            return\n        elif operator == 'equals' and not (current_value == threshold):\n            return\n        elif operator not in ('greater_than', 'less_than', 'equals'):\n            raise ValueError(f\"Invalid operator: {operator}\")\n    \n    device_config[param_name] = value\n\ndef apply_iot_device_policy(device_id, policy_json):\n    policy_data = json.loads(policy_json)\n    device_config = {\n        'device_id': device_id,\n        'max_temp': 85,\n        'alert_threshold': 75,\n        'sampling_rate': 1000\n    }\n    \n    if 'custom_rules' in policy_data:\n        rules = policy_data['custom_rules']\n        if not isinstance(rules, list):\n            raise ValueError(\"custom_rules must be a list of rule objects\")\n        for rule in rules:\n            if not isinstance(rule, dict):\n                raise ValueError(\"Each rule must be a dictionary\")\n            apply_rule(device_config, rule)\n    \n    if 'override_params' in policy_data:\n        override_params = policy_data['override_params']\n        if not isinstance(override_params, dict):\n            raise ValueError(\"override_params must be a dictionary\")\n        for param, value in override_params.items():\n            if param not in ALLOWED_PARAMS:\n                raise ValueError(f\"Disallowed parameter override: {param}\")\n            device_config[param] = validate_numeric_value(value)\n    \n    return device_config"}