T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/fetch.sh:19
- Finding
- Arbitrary Python Code Execution Through Shell-Expanded Heredoc Inputs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch.sh`, lines 19-24 **Vulnerability Type**: Python code injection through an unquoted shell heredoc **Risk Level**: High ### Vulnerable Code ```bash # Python 解析 RSS 并输出 python3 << EOF import feedparser import urllib.request import os import json import re url = "$RSS_URL" count = int("$COUNT") ``` ### Technical Analysis The script uses an unquoted heredoc delimiter (`<< EOF`). Consequently, the shell expands `$RSS_URL` and `$COUNT` before passing the heredoc body to Python. Both values are inserted directly into Python source-code contexts: - `RSS_URL` is controllable through the `AI_DAILY_RSS_URL` environment variable. - `COUNT` is controllable through the script's first positional argument or the `AI_DAILY_DEFAULT_COUNT` environment variable. Because these values are not passed as data through `sys.argv` or the process environment, an attacker can terminate the surrounding Python syntax and append arbitrary Python statements. For example, a malicious count argument could be constructed in the following form: ```text 1"); __import__("os").system("id"); # ``` After shell interpolation, the generated Python statement would be equivalent to: ```python count = int("1"); __import__("os").system("id"); #") ``` The injected statement is then executed by `python3`. Validation through `int()` does not prevent this attack because injection occurs at the Python source-code level before `int()` processes the value. ### Attack Path 1. An attacker gains the ability to influence the argument supplied to `scripts/fetch.sh`, `AI_DAILY_DEFAULT_COUNT`, or `AI_DAILY_RSS_URL`. 2. The attacker supplies a value that closes the generated Python string or expression and adds Python statements. 3. The unquoted heredoc expands the attacker-controlled value into the Python program. 4. The script invokes `python3` on the dynamically constructed program. 5. Python executes the injected statements with the privil ...[truncated 755 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Use a quoted heredoc so that the shell does not interpolate values into Python source code. Pass runtime values as command-line arguments or environment variables: ```bash python3 - "$RSS_URL" "$COUNT" <<'PY' import sys import urllib.parse url = sys.argv[1] try: count = int(sys.argv[2]) except ValueError: raise SystemExit("COUNT must be an integer") if not 1 <= count <= 100: raise SystemExit("COUNT must be between 1 and 100") parsed_url = urllib.parse.urlparse(url) if parsed_url.scheme != "https" or not parsed_url.hostname: raise SystemExit("RSS URL must be a valid HTTPS URL") # Remaining implementation PY ``` Additional hardening should include: 1. Validate `COUNT` in the shell or Python and enforce a reasonable positive upper bound. 2. Permit only HTTPS RSS URLs. 3. If arbitrary RSS sources are unnecessary, allowlist the documented official hostname. 4. Consider rejecting loopback, link-local, private-network, and cloud metadata destinations to reduce server-side request forgery risk when the RSS URL can be supplied by untrusted users. 5. Run the Skill as an unprivileged account with access only to resources needed for RSS retrieval and webhook delivery. ]]>
