T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/validate.py:151
- Finding
- Symbolic-Link Path Escape in GetX Page Generator<![CDATA[ ## Vulnerability Details **File Location**: `scripts/validate.py:151-184` **Vulnerability Type**: Symbolic-link path traversal / arbitrary file creation outside the intended directory **Risk Level**: Medium ### Vulnerable Code ```python # 计算目标目录的绝对路径,并限制在 lib 目录内 if target_dir is None: abs_target_dir = os.path.join(lib_root, "modules", name) else: # 允许绝对路径或相对项目根目录的路径 if os.path.isabs(target_dir): abs_target_dir = os.path.normpath(target_dir) else: abs_target_dir = os.path.normpath( os.path.join(project_root, target_dir) ) # 防止路径穿越:强制要求在 lib 目录下 common_prefix = os.path.commonpath([lib_root, abs_target_dir]) if common_prefix != os.path.abspath(lib_root): raise ValueError( f"目标目录不安全或超出 lib 目录范围: {target_dir!r} " f"(解析为: {abs_target_dir})" ) page_dir = os.path.join(abs_target_dir, name) # 计算用于 package import 的相对路径(相对于 lib 根目录) package_path = os.path.relpath(page_dir, lib_root).replace(os.sep, "/") os.makedirs(page_dir, exist_ok=True) files = { f"{name}_binding.dart": generate_binding(name, pascal_name, package_path), f"{name}_state.dart": generate_state(pascal_name), f"{name}_logic.dart": generate_logic(name, pascal_name), f"{name}_view.dart": generate_view(name, pascal_name), } created_files = [] for filename, content in files.items(): file_path = os.path.join(page_dir, filename) if os.path.exists(file_path): print(f"⚠️ 文件已存在,跳过: {file_path}") continue with open(file_path, 'w', encoding='utf-8') as f: f.write(content) ``` ### Technical Analysis The script claims to restrict generated files to the Flutter project's `lib` directory. However, it uses `os.path.normpath()` and `os.path.commonpath()` to perform only a lexical containment check. These functions normalize path text but do not resolve symbolic links. Consequently, a path can appear to reside under `lib` while a symbolic-link component redirects filesystem ope ...[truncated 2375 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve canonical paths before checking containment: ```python lib_root = os.path.realpath(os.path.join(project_root, "lib")) resolved_target = os.path.realpath(abs_target_dir) resolved_page = os.path.realpath(os.path.join(resolved_target, name)) if os.path.commonpath([lib_root, resolved_target]) != lib_root: raise ValueError("Target directory is outside lib") if os.path.commonpath([lib_root, resolved_page]) != lib_root: raise ValueError("Page directory is outside lib") ``` 2. Explicitly reject symbolic links in every existing path component between `lib_root` and `page_dir`, using `os.lstat()` rather than APIs that follow links. 3. Revalidate the canonical `page_dir` after directory creation. This reduces the chance that path state changes between initial validation and file creation. 4. Create output files atomically and refuse to follow symlinks. On supported platforms, use descriptor-relative operations with `os.open()` and flags such as: ```python os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW ``` 5. Avoid the `exists()` followed by `open()` check-then-use sequence. Atomic exclusive creation prevents replacement races and preserves the existing behavior of not overwriting files. 6. Add automated tests covering: - A symlink used as `target_dir`. - A symlink used as the final `page_dir`. - Nested symlink components. - A concurrent path replacement attempt. - Valid in-tree destinations to ensure normal scaffolding remains functional. ]]>
