T09 · Insecure Skill Coding Practices
- Location
scripts/uptime-log.sh:3- Finding
Arbitrary Python Code Execution Through UPTIME_LOG Injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/uptime-log.sh, lines 3–24
Vulnerability Type: Environment-variable injection into dynamically constructed Python source
Risk Level: HighVulnerable Code
bash LOG="${UPTIME_LOG:-$HOME/.config/heartbeat-pro/uptime.json}" mkdir -p "$(dirname "$LOG")" TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) EPOCH=$(date +%s) if [ ! -f "$LOG" ]; then echo '{"heartbeats":[],"total":0}' > "$LOG" fi # Append heartbeat (keep last 100) python3 -c " import json, sys with open('$LOG') as f: data = json.load(f) data['heartbeats'].append({'ts': '$TIMESTAMP', 'epoch': $EPOCH}) data['heartbeats'] = data['heartbeats'][-100:] data['total'] = data.get('total', 0) + 1 with open('$LOG', 'w') as f: json.dump(data, f) print(f'💓 Heartbeat #{data[\"total\"]} logged at $TIMESTAMP') if len(data['heartbeats']) >= 2: gap = $EPOCH - data['heartbeats'][-2]['epoch'] print(f' Gap since last: {gap//60}m {gap%60}s') " 2>/dev/null || echo "💓 Heartbeat logged at $TIMESTAMP"Technical Analysis
The script permits the
UPTIME_LOGenvironment variable to controlLOG. It then interpolates that value directly into a double-quotedpython3 -csource string:python with open('$LOG') as f: data = json.load(f)Shell quoting of
"$LOG"elsewhere in the script protects those operations from ordinary shell word splitting, but it does not make the value safe for insertion into Python source. An attacker-controlled value containing a single quote and additional Python syntax can terminate the intended Python string literal and introduce an expression that executes arbitrary code.For example, a value conceptually shaped like the following causes a Python function call to be evaluated while constructing the argument to
open():text ' + (__import__('os').system('ATTACKER_COMMAND') or '') + 'Once interpolated, the generated source conta ...[truncated 2100 chars]
- Remediation
View remediation
Remediation Suggestions
Do not interpolate filesystem paths or other externally influenced values into dynamically constructed Python source. Pass the log path as a positional argument:
bash python3 - "$LOG" "$TIMESTAMP" "$EPOCH" <<'PY' import json import sys log_path = sys.argv[1] timestamp = sys.argv[2] epoch = int(sys.argv[3]) with open(log_path, encoding="utf-8") as handle: data = json.load(handle) data.setdefault("heartbeats", []).append({ "ts": timestamp, "epoch": epoch, }) data["heartbeats"] = data["heartbeats"][-100:] data["total"] = data.get("total", 0) + 1 with open(log_path, "w", encoding="utf-8") as handle: json.dump(data, handle) PYAdditional hardening measures should include:
- If custom log locations are unnecessary, remove
UPTIME_LOGsupport and always use the fixed application directory. - If custom paths are required, canonicalize the path and optionally restrict it to an approved directory.
- Create the configuration directory with restrictive permissions, such as
mkdir -p -m 700. - Create log files with an appropriate restrictive umask, such as
umask 077. - Stop suppressing all Python errors. Return a nonzero status and an accurate error message without exposing sensitive data.
- Use atomic file replacement and locking to avoid corruption when concurrent heartbeat processes update the same JSON file.
- Validate the JSON structure and recover safely from malformed or partially written data.
- If custom log locations are unnecessary, remove
