T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/full_workflow.py:166
- Finding
- Arbitrary Shell Command Injection Through Unvalidated Preference Values<![CDATA[ ## Vulnerability Details **File Location**: `scripts/full_workflow.py:24-32, 40-47, 166-174, 185-193`; configuration source in `scripts/preference_learner.py:39-47` **Vulnerability Type**: OS command injection through configuration values interpolated into shell commands **Risk Level**: High ### Vulnerable Code Configuration values are loaded from an editable JSON file without schema or character validation: ```python def load_preferences(): """加载用户偏好""" if PREFERENCE_FILE.exists(): with open(PREFERENCE_FILE, 'r', encoding='utf-8') as f: prefs = json.load(f) # 合并默认值(防止新版本缺少字段) return deep_merge(DEFAULT_PREFERENCES, prefs) return DEFAULT_PREFERENCES.copy() ``` The resulting values are assigned to global variables: ```python TARGET_DURATION = prefs["video"]["target_duration"] HORIZONTAL_FONT_SIZE = prefs["subtitles"]["horizontal_font_size"] VERTICAL_FONT_SIZE = prefs["subtitles"]["vertical_font_size"] FONT_NAME = prefs["subtitles"]["font_name"] AUTO_WRAP = prefs["subtitles"]["auto_wrap"] MARGIN_V = prefs["subtitles"]["margin_v"] BG_MUSIC_VOLUME = prefs["audio"]["background_music_volume"] FADE_DURATION = prefs["audio"]["fade_in_duration"] ``` Commands are then passed to a system shell: ```python def run_cmd(cmd, description=""): """运行命令并打印进度""" if description: print(f"🎬 {description}...") result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if result.returncode != 0: print(f"❌ 失败:{result.stderr}") return False return True ``` For example, the unvalidated `FONT_NAME` value is embedded directly into an FFmpeg shell command: ```python def add_subtitles_to_video(): """添加字幕到视频(使用 ffmpeg-full + libass)""" print("\n📝 步骤 4: 烧录字幕到视频(横屏 16:9,720p)") # 使用 ffmpeg-full(支持 libass 字幕) ffmpeg_full = "/usr/local/Cellar/ffmpeg-full/8.0.1_3/bin/ffmpeg" # 字幕样式:从偏好读取,输出 720p (1280x720) wrap_style = "2" if not AUTO ...[truncated 3621 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Eliminate shell interpretation by constructing argument arrays and using `shell=False`: ```python subprocess.run( [ ffmpeg_full, "-y", "-i", str(VIDEO_FILE), "-i", str(TEMP_DIR / "mixed_audio.aac"), "-vf", filter_value, "-c:a", "copy", str(OUTPUT_FILE), ], shell=False, capture_output=True, text=True, check=False, ) ``` 2. Apply the same argument-list approach to every FFmpeg and FFprobe invocation. Do not attempt to solve the issue solely through manual shell escaping. 3. Validate the complete preference document before use: - Require `font_name` to be a string matching a conservative allowlist, such as letters, digits, spaces, underscores, and hyphens. - Require durations, font sizes, margins, and volume values to be numeric. - Enforce safe ranges, including positive durations, bounded font sizes and margins, volume between `0` and `1`, and fade durations no greater than the target duration. - Require Boolean values for Boolean settings. - Reject unknown keys where practical. 4. Handle malformed JSON and validation failures explicitly rather than continuing with partially trusted values. 5. Restrict the preference file to the owning user where the deployment environment warrants it. 6. Add regression tests using quotes, semicolons, command substitutions, newlines, and other shell metacharacters to verify that all values remain literal FFmpeg arguments. ]]>
