T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/douyin_download.py:114
- Finding
- OS Command Injection Through the Input URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/douyin_download.py`, lines 20-22, 65-66, and 114 **Vulnerability Type**: Shell command injection **Risk Level**: High ### Vulnerable Code ```python def run(cmd: str, timeout: int = 30) -> str: """Run a shell command and return stdout.""" r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) return r.stdout.strip() ``` ```python # Fallback: try as-is return url, None ``` ```python out = run_check(f"agent-browser open '{url}'", timeout=args.timeout) ``` The corresponding command execution helper also enables shell interpretation: ```python def run_check(cmd: str, timeout: int = 30) -> str: """Run a shell command and raise on failure.""" r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) if r.returncode != 0: raise RuntimeError(f"Command failed: {cmd}\n{r.stderr}") return r.stdout.strip() ``` ### Technical Analysis The positional URL argument is inserted into a shell command inside single quotes. The application attempts no shell escaping before passing the resulting string to `subprocess.run()` with `shell=True`. The URL normalization function also returns unsupported URLs unchanged. Consequently, a single quote in a supplied value can terminate the quoted argument. Shell metacharacters following that quote are then interpreted as commands by the operating-system shell. This is not limited to URLs from the documented Douyin domains because the fallback behavior accepts an arbitrary value as-is. ### Attack Path 1. An attacker causes the Skill to be invoked with a URL containing a single quote and shell syntax, such as a value conceptually shaped like: ```text https://example.invalid/x'; attacker_command; # ``` 2. `normalize_url()` does not recognize it as a supported Douyin URL and returns the entire value unchanged. 3. The program constructs a command resembling: ```sh agen ...[truncated 684 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Eliminate shell interpretation and pass each command argument separately: ```python def run_check(args: list[str], timeout: int = 30) -> str: result = subprocess.run( args, shell=False, capture_output=True, text=True, timeout=timeout, check=True, ) return result.stdout.strip() out = run_check(["agent-browser", "open", url], timeout=args.timeout) ``` In addition: 1. Require the URL scheme to be `https`. 2. Normalize the hostname using `urllib.parse`. 3. Allow only exact approved hosts, such as `www.douyin.com` and `v.douyin.com`, or correctly bounded subdomains if operationally required. 4. Reject URLs containing credentials, unsupported ports, control characters, or malformed hostnames. 5. Remove the arbitrary-URL fallback and fail closed when the input does not match a supported Douyin URL format. 6. Never rely on manually adding quotes to make untrusted data safe for a shell. ]]>
