T09 · Insecure Skill Coding Practices
- Location
download_video.py:31- Finding
Untrusted URL Is Interpreted as yt-dlp Command-Line Options
- Content
View full analysis
dict: """Fetch metadata without downloading.""" result = subprocess.run( ["yt-dlp", "--dump-single-json", "--no-playlist", url], check=True, text=True, capture_output=True, ) return json.loads(result.stdout) def build_command(args: argparse.Namespace) -> list[str]: """Build the yt-dlp command from parsed arguments.""" output_dir = Path(args.output).expanduser() output_template = str(output_dir / "%(title)s [%(id)s].%(ext)s") cmd = ["yt-dlp", "--no-playlist", "--no-progress"] if args.audio_only: cmd.extend(["-x", "--audio-format", "mp3", "--audio-quality", "0"]) else: cmd.extend(["-f", format_selector(args.quality), "--merge-output-format", args.format]) if args.restrict_filenames: cmd.append("--restrict-filenames") cmd.extend(["-o", output_template, args.url]) return cmd ``` ### Technical Analysis The purported URL is appended directly to two `yt-dlp` command lines without an end-of-options delimiter such as `--`. A value beginning with `-` can therefore be parsed by `yt-dlp` as an option rather than as a media URL. Python's `subprocess.run` is correctly invoked with an argument list and without `shell=True`, so ordinary shell metacharacters do not produce shell injection. However, using a list does not prevent injection into the invoked program's own command-line parser. The wrapper's `argparse` configuration normally rejects an option-like positional value. Nevertheless, a caller can explicitly terminate the wrapper's options and pass such a value as the positional argument: ```bash python3 download_video.py -- '--batch-file=/tmp/attacker-list.txt' ``` The resulting metadata command is effectively: ```bash yt-dlp --d ...[truncated 2668 chars]- Remediation
View remediation
str: parsed = urlparse(value) if parsed.scheme not in {"https", "http"} or not parsed.hostname: raise ValueError("A valid HTTP or HTTPS video URL is required.") return value ``` 3. Explicitly reject values beginning with `-`, even after URL parsing, as defense in depth. 4. Consider adding `--ignore-config` to both yt-dlp invocations if ambient user or system yt-dlp configurations are not required. This prevents unrelated configuration files from silently changing the wrapper's reviewed behavior: ```python ["yt-dlp", "--ignore-config", "--dump-single-json", "--no-playlist", "--", url] ``` 5. Add regression tests covering: - `--batch-file=...` - `--config-locations=...` - `--exec=...` - malformed URLs - unsupported schemes - valid HTTP and HTTPS URLs 6. Continue using argument-list subprocess execution with `shell=False`; do not replace it with a shell command string. ]]>
