T09 · Insecure Skill Coding Practices
- Location
scripts/download_video.py:253- Finding
Untrusted creator nickname can escape the intended audio output directory
- Content
View full analysis
|]', "_", title) save_path = AUDIO_DIR / blogger / f"{video_id}.m4a" ``` The creator nickname originates from MediaCrawler output and is copied into the normalized dataset without path validation: ```python nickname = item.get("nickname") or blogger_name records.append({ "博主昵称": nickname, "视频标题": item.get("title") or item.get("desc", ""), "视频链接": item.get("aweme_url", ""), ... }) ``` ### Technical Analysis The `blogger` value is remotely sourced creator metadata. It is used directly as a filesystem path component, while only the unrelated `title` value is sanitized. `pathlib.Path` does not automatically confine joined paths to the original base directory. Values containing directory separators, parent-directory components such as `..`, or an absolute path can cause `save_path` to resolve outside `AUDIO_DIR`. The resulting path is passed to ffmpeg with the overwrite option: ```python [ FFMPEG_EXE, "-headers", headers, "-i", video_url, "-vn", "-acodec", "copy", "-y", str(save_path), ] ``` The retry error path may also delete the selected file: ```python if save_path.exists(): save_path.unlink() ``` Although subtitle filenames are sanitized elsewhere, that protection is not applied at this audio-writing sink. ### Attack Path 1. An attacker controls or influences a tracked Douyin creator nickname. 2. MediaCrawler returns the nickname in creator metadata. 3. `scrape_profile.py` stores it in the `博主昵称` CSV field. 4. `clean_data.py` renames the field to `博主` without sanitizing its contents. 5. `download_video.py` joins the untrusted valu ...[truncated 795 chars]- Remediation
View remediation
str: value = re.sub(r'[\\/:*?"<>|\x00-\x1f]', "_", str(value)).strip() value = value.strip(". ") if not value or value in {".", ".."}: return fallback return value[:100] safe_blogger = safe_component(blogger, "unknown_creator") save_path = (AUDIO_DIR / safe_blogger / f"{video_id}.m4a").resolve() audio_root = AUDIO_DIR.resolve() if save_path.parent != audio_root and audio_root not in save_path.parents: raise ValueError("Audio output path escapes AUDIO_DIR") ``` 3. Validate `video_id` against the expected Douyin identifier format before using it as a filename. 4. Perform downloads into a securely created temporary file under `AUDIO_DIR`, validate the result, and atomically rename it to the final destination. 5. Do not unlink a path unless its resolved location has first been verified to remain under the approved audio directory. ]]>
