T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/analyze_stock.py:48
- Finding
- Path Traversal Through Unsanitized Default Report Filename<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyze_stock.py:48-55`; additional affected entry point: `core/stock_analyzer.py:365-368` **Vulnerability Type**: Path traversal and unintended file overwrite **Risk Level**: Medium ### Vulnerable Code `scripts/analyze_stock.py:48-55`: ```python if args.output: output_path = args.output else: from datetime import datetime output_path = f"{args.stock}_分析报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" with open(output_path, 'w', encoding='utf-8') as f: f.write(report) ``` `core/stock_analyzer.py:365-368`: ```python output_file = f"{stock_query}_分析报告_{datetime.now().strftime('%Y%m%d')}.txt" with open(output_file, 'w', encoding='utf-8') as f: f.write(report) print(f"\n报告已保存到: {output_file}") ``` ### Technical Analysis Both command-line entry points incorporate an untrusted stock argument directly into the default output filename. The input is not restricted to a stock-code format, stripped of path separators, or resolved against a dedicated report directory. Consequently, values containing `../`, absolute-path components where supported, or platform-specific path separators can influence where the report is written. Python opens the resulting path in `w` mode, which creates a new file or truncates an existing file. The automatically appended timestamp and suffix constrain the exact filename an attacker can target, but they do not prevent directory traversal or unintended file creation outside the project directory. The explicit `--output` option is intentionally designed to accept a caller-selected path. The vulnerability concerns the supposedly safe default path derived from the positional stock argument. ### Attack Path 1. An attacker or untrusted caller supplies a stock value containing traversal components, such as `../../target`. 2. The application performs stock analysis using that string. 3. The same unvalidated string is interpolated into the default report fi ...[truncated 1002 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Validate stock identifiers against a strict allowlist where possible, such as letters, digits, dots, underscores, and hyphens. 2. Convert display names to safe filenames by removing path separators, `..`, control characters, and platform-specific reserved characters. 3. Write default reports beneath a dedicated directory such as `reports/`. 4. Resolve both the report directory and candidate path to canonical absolute paths, then verify that the candidate remains inside the report directory. 5. Create the destination directory with restrictive permissions. 6. Consider exclusive file creation with mode `x` or explicit overwrite confirmation when overwriting is unnecessary. 7. Apply the same helper function to both affected entry points. Example: ```python from pathlib import Path import re REPORT_DIR = (Path(__file__).resolve().parent.parent / "reports").resolve() REPORT_DIR.mkdir(mode=0o700, parents=True, exist_ok=True) safe_stock = re.sub(r"[^A-Za-z0-9._-]", "_", args.stock) safe_stock = safe_stock.replace("..", "_").strip("._") if not safe_stock: raise ValueError("Invalid stock identifier") candidate = ( REPORT_DIR / f"{safe_stock}_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" ).resolve() if REPORT_DIR not in candidate.parents: raise ValueError("Output path escapes the report directory") with candidate.open("x", encoding="utf-8") as report_file: report_file.write(report) ``` ]]>
