T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/notify-linux.sh:11
- Finding
- Python Code Injection Through Unvalidated Script Arguments in Linux Notification Script<![CDATA[ ## Vulnerability Details **File Location**: `scripts/notify-linux.sh`, lines 11-18 **Vulnerability Type**: Python code injection caused by unsafe string interpolation **Risk Level**: Medium ### Vulnerable Code ```bash # Load config if provided if [ -n "$CONFIG_PATH" ] && [ -f "$CONFIG_PATH" ]; then if command -v jq &>/dev/null; then CUSTOM_SOUND=$(jq -r ".sounds.${TYPE} // empty" "$CONFIG_PATH" 2>/dev/null) elif command -v python3 &>/dev/null; then CUSTOM_SOUND=$(python3 -c "import json; c=json.load(open('$CONFIG_PATH')); print(c.get('sounds',{}).get('$TYPE',''))" 2>/dev/null) fi fi ``` ### Technical Analysis The script accepts `TYPE` and `CONFIG_PATH` as command-line arguments and directly interpolates both values into Python source passed to `python3 -c`. Shell quoting does not make this safe because the interpolated data is inserted inside Python string literals. An attacker can include quotes, parentheses, semicolons, and Python expressions in either argument to terminate the intended expression and append arbitrary Python statements. The vulnerable branch is reached when: 1. The supplied configuration path references an existing file. 2. `jq` is unavailable. 3. `python3` is available. The configured hooks normally pass fixed notification types, but the script is also documented for standalone invocation and does not enforce an allowlist for `TYPE`. ### Attack Path 1. The attacker causes the script to be invoked with an existing JSON configuration file. 2. The environment does not have `jq`, causing the Python fallback to run. 3. The attacker supplies a crafted `TYPE`, for example: ```bash bash scripts/notify-linux.sh \ "x','')); __import__('os').system('touch /tmp/agent-notify-pwned'); #" \ ./config/default.json ``` 4. The generated Python program is equivalent to an expression containing: ```python __import__('os').system('touch /tmp/agent-notify-pwned') ``` 5. Python executes the injected operating- ...[truncated 836 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Keep Python source constant and pass all dynamic values as positional arguments: ```bash CUSTOM_SOUND=$( python3 -c ' import json import sys with open(sys.argv[1], encoding="utf-8") as handle: config = json.load(handle) print(config.get("sounds", {}).get(sys.argv[2], "")) ' "$CONFIG_PATH" "$TYPE" 2>/dev/null ) ``` Additionally: 1. Restrict `TYPE` to the supported values before processing it: ```bash case "$TYPE" in confirm|done|error|default) ;; *) echo "Unsupported notification type" >&2; exit 2 ;; esac ``` 2. Resolve and validate the configuration path if callers are not intended to select arbitrary files. 3. Fail safely when JSON parsing fails rather than continuing with partially initialized data. 4. Add regression tests containing quotes, semicolons, command substitutions, newlines, and Python syntax in both arguments. ]]>
