T09 · Insecure Skill Coding Practices
- Location
scripts/create_clip.sh:9- Finding
Predictable Shared Temporary File Enables Symlink Overwrite and Cooldown Manipulation
- Content
View full analysis
&2 exit 1 fi # Cooldown check — prevent spam clipping if [ -f "$LOCKFILE" ]; then LAST_CLIP=$(cat "$LOCKFILE") NOW=$(date +%s) ELAPSED=$(( NOW - LAST_CLIP )) REMAINING=$(( COOLDOWN_SECONDS - ELAPSED )) if [ "$ELAPSED" -lt "$COOLDOWN_SECONDS" ]; then echo "COOLDOWN: Please wait ${REMAINING}s before clipping again." >&2 exit 2 fi fi RESPONSE=$(curl -s -X POST \ "https://api.twitch.tv/helix/clips?broadcaster_id=${BROADCASTER_ID}&has_delay=false" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Client-Id: ${CLIENT_ID}") CLIP_ID=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['data'][0]['id'])" 2>/dev/null) EDIT_URL=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['data'][0]['edit_url'])" 2>/dev/null) if [ -z "$CLIP_ID" ]; then echo "ERROR: Failed to create clip. Response: $RESPONSE" >&2 exit 1 fi # Write cooldown timestamp on success date +%s > "$LOCKFILE" ``` ### Technical Analysis The cooldown state is stored at the fixed path `/tmp/twitch_clip_cooldown`. On multi-user systems, `/tmp` is normally globally writable. The script neither creates a private state directory nor verifies the ownership, type, or symlink status of this path. The test `[ -f "$LOCKFILE" ]`, the subsequent `cat`, and the final shell redirection all follow symbolic links. Consequently, another local user can create or replace the cooldown path before the Skill executes. The final redirection opens the resolved target with truncation enabled and writes the current epoch timestamp to it. An attacker can ...[truncated 2337 chars]- Remediation
View remediation
&2 exit 1 fi ``` 4. Serialize the entire cooldown check, Twitch request, and timestamp update with `flock` or an equivalent locking mechanism. Locking only the final write does not prevent concurrent requests from bypassing the cooldown. 5. Write updates atomically to a securely created temporary file in the same private directory, then rename it into place: ```bash tmp=$(mktemp "$STATE_DIR/cooldown.XXXXXX") || exit 1 printf '%s\n' "$(date +%s)" > "$tmp" chmod 600 "$tmp" mv -fT -- "$tmp" "$LOCKFILE" ``` 6. Validate cooldown content before arithmetic use. Accept only a bounded sequence of decimal digits and fail safely if validation fails: ```bash case "$LAST_CLIP" in ''|*[!0-9]*) echo "ERROR: Invalid cooldown state" >&2 exit 1 ;; esac ``` These changes isolate state to the invoking user, prevent symlink-following attacks, reduce race conditions, and preserve the intended anti-spam behavior. ]]>
