{"title":"Command Injection via os.system() with Untrusted Input","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[4,7],"flow_lines":[4,11,5,16,4,19,6,16,7,16],"sink_lines":[11,16,19],"vulnerable_code":"import os\nimport json\n\ndef provision_iot_device(device_config):\n    device_id = device_config.get('device_id')\n    firmware_version = device_config.get('firmware_ver')\n    network_ssid = device_config.get('wifi_ssid')\n    device_type = device_config.get('type')\n    \n    config_path = f\"/opt/iot/devices/{device_id}/config.json\"\n    os.system(f\"mkdir -p /opt/iot/devices/{device_id}\")\n    \n    with open(config_path, 'w') as cfg:\n        json.dump(device_config, cfg)\n    \n    provisioning_cmd = f\"iot-provision --device={device_id} --firmware={firmware_version} --ssid={network_ssid} --type={device_type}\"\n    result = os.system(provisioning_cmd)\n    \n    if result == 0:\n        os.system(f\"echo 'Device {device_id} provisioned successfully' >> /var/log/iot_provision.log\")\n        return {\"status\": \"success\", \"device_id\": device_id}\n    else:\n        return {\"status\": \"failed\", \"device_id\": device_id}","explanation":"The function accepts untrusted input through device_config parameters (device_id, firmware_version, network_ssid, device_type) and directly interpolates them into shell commands executed via os.system() without any validation or sanitization. An attacker can inject arbitrary shell commands through any of these parameters using shell metacharacters like semicolons, pipes, or backticks.","remediation":"The fix replaces all os.system() calls with safe alternatives: subprocess.run() with argument lists (preventing shell interpretation), os.makedirs() for directory creation, and Python's logging module for log writing. All input parameters are validated against strict regex patterns before use, rejecting any values containing shell metacharacters or unexpected characters.","secure_code":"import os\nimport json\nimport subprocess\nimport re\nimport logging\n\nlogger = logging.getLogger('iot_provision')\nlogging.basicConfig(filename='/var/log/iot_provision.log', level=logging.INFO)\n\ndef validate_device_id(device_id):\n    if not device_id or not re.match(r'^[a-zA-Z0-9_\\-]+$', device_id):\n        raise ValueError(f\"Invalid device_id: must be alphanumeric with hyphens/underscores only\")\n    return device_id\n\ndef validate_firmware_version(firmware_ver):\n    if not firmware_ver or not re.match(r'^[a-zA-Z0-9\\.\\-]+$', firmware_ver):\n        raise ValueError(f\"Invalid firmware version: must be alphanumeric with dots/hyphens only\")\n    return firmware_ver\n\ndef validate_ssid(ssid):\n    if not ssid or len(ssid) > 32 or any(c in ssid for c in [';', '|', '&', '`', '$', '(', ')', '{', '}', '\\n', '\\r']):\n        raise ValueError(f\"Invalid SSID: contains disallowed characters or exceeds length\")\n    return ssid\n\ndef validate_device_type(device_type):\n    if not device_type or not re.match(r'^[a-zA-Z0-9_\\-]+$', device_type):\n        raise ValueError(f\"Invalid device type: must be alphanumeric with hyphens/underscores only\")\n    return device_type\n\ndef provision_iot_device(device_config):\n    device_id = validate_device_id(device_config.get('device_id'))\n    firmware_version = validate_firmware_version(device_config.get('firmware_ver'))\n    network_ssid = validate_ssid(device_config.get('wifi_ssid'))\n    device_type = validate_device_type(device_config.get('type'))\n    \n    config_dir = os.path.join('/opt/iot/devices', device_id)\n    config_path = os.path.join(config_dir, 'config.json')\n    \n    os.makedirs(config_dir, exist_ok=True)\n    \n    with open(config_path, 'w') as cfg:\n        json.dump(device_config, cfg)\n    \n    provisioning_cmd = [\n        'iot-provision',\n        f'--device={device_id}',\n        f'--firmware={firmware_version}',\n        f'--ssid={network_ssid}',\n        f'--type={device_type}'\n    ]\n    result = subprocess.run(provisioning_cmd, capture_output=True, text=True)\n    \n    if result.returncode == 0:\n        logger.info(f'Device {device_id} provisioned successfully')\n        return {\"status\": \"success\", \"device_id\": device_id}\n    else:\n        logger.error(f'Device {device_id} provisioning failed: {result.stderr}')\n        return {\"status\": \"failed\", \"device_id\": device_id}"}