{"title":"Insecure Subprocess Command Injection via Shell=True with Unsanitized Environment Variable Expansion","language":"Python","severity":"Critical","cwe":"CWE-78","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"import subprocess\nimport os\ndef fetch_iot_device_metrics(device_id, metric_type):\n    os.environ['DEVICE_TARGET'] = device_id\n    os.environ['METRIC_KEY'] = metric_type\n    cmd_template = 'iot-cli poll --target=$DEVICE_TARGET --metric=$METRIC_KEY --format=json'\n    proc = subprocess.run(cmd_template, shell=True, capture_output=True, text=True, env=os.environ.copy())\n    if proc.returncode != 0:\n        raise RuntimeError(f'Polling failed: {proc.stderr}')\n    return proc.stdout","explanation":"The function accepts unsanitized user-controlled inputs `device_id` and `metric_type` (lines 3-4), stores them in environment variables `DEVICE_TARGET` and `METRIC_KEY` (lines 4-5), then passes them into a shell command string with `shell=True` (line 7). Because the shell expands `$DEVICE_TARGET` and `$METRIC_KEY` at runtime, an attacker can embed shell metacharacters (e.g., `;`, `$()`, `&&`) inside those values to inject arbitrary commands that the shell will execute.","remediation":"The fix eliminates shell=True by passing the command as a list of explicit arguments, so no shell expansion or metacharacter interpretation occurs. It also removes the mutation of os.environ (which was the injection vector) and adds strict input validation via allowlist regex to reject any device_id or metric_type containing shell metacharacters or unexpected characters.","secure_code":"import subprocess\nimport re\n\ndef fetch_iot_device_metrics(device_id, metric_type):\n    # Validate device_id: allow only alphanumeric characters, hyphens, and underscores\n    if not re.fullmatch(r'[A-Za-z0-9_\\-]+', device_id):\n        raise ValueError(f'Invalid device_id: {device_id!r}')\n    # Validate metric_type: allow only alphanumeric characters, hyphens, and underscores\n    if not re.fullmatch(r'[A-Za-z0-9_\\-]+', metric_type):\n        raise ValueError(f'Invalid metric_type: {metric_type!r}')\n\n    cmd = [\n        'iot-cli', 'poll',\n        f'--target={device_id}',\n        f'--metric={metric_type}',\n        '--format=json'\n    ]\n    proc = subprocess.run(cmd, capture_output=True, text=True)\n    if proc.returncode != 0:\n        raise RuntimeError(f'Polling failed: {proc.stderr}')\n    return proc.stdout"}