T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/mood_tracker.sh:31
- Finding
- Python Code Injection Through Mood Score and Note Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mood_tracker.sh`, lines 31-43 **Vulnerability Type**: Python source-code injection through unsafe shell-variable interpolation **Risk Level**: High ### Vulnerable Code ```bash log) score="${1:-3}"; note="${2:-}" python3 << PYEOF import json, time with open("$DB") as f: data = json.load(f) emojis = {1:"😢",2:"😕",3:"😐",4:"🙂",5:"😄"} labels = {1:"Terrible",2:"Not Great",3:"Okay",4:"Good",5:"Amazing"} data.append({"score":int("$score"),"emoji":emojis.get(int("$score"),"😐"), "label":labels.get(int("$score"),"?"),"note":"$note", "date":time.strftime("%Y-%m-%d"),"time":time.strftime("%H:%M"), "weekday":time.strftime("%A")}) with open("$DB","w") as f: json.dump(data, f, indent=2) print("{} Mood: {} ({}/5) {}".format(emojis.get(int("$score")), labels.get(int("$score")), "$score", "$note")) PYEOF ;; ``` ### Technical Analysis The unquoted heredoc delimiter permits Bash to expand `$score`, `$note`, and `$DB` before Python parses the generated program. Consequently, the command arguments are treated as Python source code rather than inert data. A crafted score can escape an `int("...")` expression and introduce another Python expression. A crafted note can terminate or extend its surrounding string expression. For example, a note shaped as a concatenated Python expression can cause `__import__("os").system(...)` to be evaluated while Python constructs the dictionary or output string. The script performs no Bash-level validation that the score is an integer from 1 through 5. Python conversion with `int()` does not provide injection protection because the attacker-controlled value has already been inserted into the source code before `int()` executes. ### Attack Path 1. An attacker supplies or persuades a user or Agent to supply a specially crafted `score` or `note` argument to the `log` command. 2. Bash expands the argument inside the unquoted Python here ...[truncated 869 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Pass user input as arguments rather than interpolating it into Python source. Use a single-quoted heredoc delimiter so Bash performs no expansion: ```bash score="${1:-3}" note="${2:-}" case "$score" in 1|2|3|4|5) ;; *) echo "Score must be an integer from 1 through 5" >&2; exit 1 ;; esac python3 - "$DB" "$score" "$note" <<'PYEOF' import json import sys import time db, score_text, note = sys.argv[1:] score = int(score_text) with open(db, encoding="utf-8") as f: data = json.load(f) emojis = {1: "😢", 2: "😕", 3: "😐", 4: "🙂", 5: "😄"} labels = {1: "Terrible", 2: "Not Great", 3: "Okay", 4: "Good", 5: "Amazing"} data.append({ "score": score, "emoji": emojis[score], "label": labels[score], "note": note, "date": time.strftime("%Y-%m-%d"), "time": time.strftime("%H:%M"), "weekday": time.strftime("%A"), }) with open(db, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) PYEOF ``` Apply the same data/source separation to every embedded Python block. Add automated tests using quotes, backslashes, newlines, and Python-like expressions in notes to verify that they remain plain data. ]]>
