{"title":"Command Injection via subprocess.shell","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[3],"flow_lines":[3,4,3,6],"sink_lines":[4,6],"vulnerable_code":"import subprocess\n\ndef rotate_iot_device_logs(device_id, log_format):\n    archive_cmd = f\"tar -czf /var/iot/logs/{device_id}.tar.gz --format={log_format} /var/iot/logs/{device_id}/\"\n    result = subprocess.run(archive_cmd, shell=True, capture_output=True, text=True)\n    if result.returncode == 0:\n        cleanup_cmd = f\"find /var/iot/logs/{device_id}/ -type f -mtime +7 -exec rm {{}} \\;\"\n        subprocess.run(cleanup_cmd, shell=True)\n        return {\"status\": \"success\", \"archive\": f\"{device_id}.tar.gz\"}\n    return {\"status\": \"failed\", \"error\": result.stderr}","explanation":"The function uses unsanitized user input (device_id and log_format parameters) directly in shell commands via f-strings, then executes them with subprocess.run(shell=True). An attacker can inject arbitrary shell commands through either parameter to achieve remote code execution on the IoT platform.","remediation":"The fix eliminates command injection by: (1) validating device_id against a strict whitelist regex pattern allowing only safe characters, (2) validating log_format against an explicit allowlist of known tar formats, and (3) replacing shell=True with argument lists passed directly to subprocess.run, which prevents shell interpretation of special characters.","secure_code":"import subprocess\nimport re\nimport os\n\ndef rotate_iot_device_logs(device_id, log_format):\n    # Validate device_id: only allow alphanumeric characters, hyphens, and underscores\n    if not re.match(r'^[a-zA-Z0-9_\\-]+$', device_id):\n        return {\"status\": \"failed\", \"error\": \"Invalid device_id: only alphanumeric characters, hyphens, and underscores are allowed\"}\n    \n    # Validate log_format: only allow known safe format values\n    allowed_formats = ['gnu', 'oldgnu', 'pax', 'posix', 'ustar', 'v7']\n    if log_format not in allowed_formats:\n        return {\"status\": \"failed\", \"error\": f\"Invalid log_format: must be one of {allowed_formats}\"}\n    \n    # Validate paths exist\n    log_dir = os.path.join('/var/iot/logs', device_id)\n    archive_path = os.path.join('/var/iot/logs', f'{device_id}.tar.gz')\n    \n    if not os.path.isdir(log_dir):\n        return {\"status\": \"failed\", \"error\": \"Log directory does not exist\"}\n    \n    # Use subprocess with argument list (no shell=True) to prevent command injection\n    archive_cmd = [\n        'tar', '-czf', archive_path,\n        f'--format={log_format}',\n        log_dir\n    ]\n    result = subprocess.run(archive_cmd, capture_output=True, text=True)\n    \n    if result.returncode == 0:\n        cleanup_cmd = [\n            'find', log_dir,\n            '-type', 'f',\n            '-mtime', '+7',\n            '-exec', 'rm', '{}', ';'\n        ]\n        subprocess.run(cleanup_cmd)\n        return {\"status\": \"success\", \"archive\": f\"{device_id}.tar.gz\"}\n    return {\"status\": \"failed\", \"error\": result.stderr}"}