T09 · Insecure Skill Coding Practices
- Location
scripts/news_broadcast.sh:136- Finding
Arbitrary Python Code Execution Through an Unvalidated News Limit
- Content
View full analysis
Vulnerability Details
File Location:
scripts/news_broadcast.sh:48-59, 136-145, 174-177
Vulnerability Type: Code injection through dynamic Python source construction
Risk Level: HighVulnerable Code
bash while [[ $# -gt 0 ]]; do case $1 in -c|--config) CONFIG_FILE="$2" shift 2 ;; -o|--output) OUTPUT_TYPE="$2" shift 2 ;; --limit) NEWS_LIMIT="$2" shift 2 ;;bash limit=${limit:-$NEWS_LIMIT} local news=$(search_news "$category" "$keywords" "$limit")bash echo "$result" | python3 -c " import sys, json try: data = json.load(sys.stdin) results = data.get('results', []) for i, r in enumerate(results[:$limit], 1): title = r.get('title', 'No title') content = r.get('content', '')[:200] print(f'{i}. {title}')Technical Analysis
The
limitvalue originates either from the third field of the user-controlled configuration file or from the--limitcommand-line argument. The script performs no numeric validation before directly interpolating this value into source code passed topython3 -c.Consequently,
limitis treated as a Python expression rather than as data. An attacker able to modify the configuration file or invocation arguments can supply an expression such as:text __import__('os').system('id') or 1This produces an executable expression equivalent to:
python results[:__import__('os').system('id') or 1]Python evaluates the injected function call while calculating the slice boundary. The vulnerability is reached when
TAVILY_API_KEYis configured and the Tavily request returns a non-empty response.Attack Path
- The attacker gains the ability to modify the news configuration file or influence the arguments passed to the ...[truncated 1407 chars]
- Remediation
View remediation
Remediation Suggestions
-
Validate both command-line and configuration-derived limits as decimal integers:
bash validate_limit() { local value="$1" if [[ ! "$value" =~ ^[0-9]+$ ]] || (( value < 1 || value > 100 )); then echo "Invalid news limit" >&2 exit 1 fi } validate_limit "$limit" -
Never interpolate external values into Python source. Pass the limit as a positional argument:
bash python3 -c ' import json import sys limit = int(sys.argv[1]) data = json.load(sys.stdin) results = data.get("results", []) for i, result in enumerate(results[:limit], 1): title = result.get("title", "No title") content = result.get("content", "")[:200] print(f"{i}. {title}") print(f" {content}...") print() ' "$limit" -
Apply the same validation immediately after parsing
--limitand after reading each configuration row, rather than relying only on validation at the final sink. -
Run the scheduled task under a dedicated, unprivileged account with access only to the required configuration and output locations. Do not run it as root.
-
Protect the configuration file from modification by less-trusted users and avoid exposing API credentials to processes that do not require them.
-
Add regression tests covering nonnumeric values, Python expressions, negative numbers, excessive limits, missing arguments, and malformed configuration rows.
-
