T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/convert_v2.py:267
- Finding
- Untrusted attachment paths can copy arbitrary local files into the Obsidian vault<![CDATA[ ## Vulnerability Details **File Location**: `scripts/convert_v2.py:267-292` **Vulnerability Type**: Path traversal and unauthorized local file access **Risk Level**: High ### Vulnerable Code ```python def _process_attachments(self, notes: List[FlomoNote]) -> int: """处理并复制附件""" copied_count = 0 for note in notes: for attachment in note.attachments: src_path = self.flomo_html_dir / attachment['src'] if not src_path.exists(): logger.warning(f"附件不存在: {src_path}") continue # 生成新的文件名 filename = src_path.name dest_path = self.attachments_dir / filename # 如果文件已存在,添加时间戳避免冲突 if dest_path.exists(): stem = dest_path.stem suffix = dest_path.suffix timestamp = note.datetime.strftime('%Y%m%d%H%M%S') dest_path = self.attachments_dir / f"{stem}_{timestamp}{suffix}" try: shutil.copy2(src_path, dest_path) attachment['obsidian_path'] = f"attachments/{dest_path.name}" copied_count += 1 ``` The attachment source value is extracted directly from imported HTML: ```python src = img.get('src', '') if src: attachments.append({ 'type': 'image', 'src': src, 'alt': img.get('alt', 'image') }) ``` ### Technical Analysis The converter treats an attachment `src` attribute from an imported HTML document as a trusted filesystem path. Joining an attacker-controlled path with `self.flomo_html_dir` does not guarantee containment. A relative value containing traversal components, such as `../../sensitive-file`, can resolve outside the export directory. A platform-supported absolute path may also override or bypass the intended base directory. The code checks only whether the resulting path exists; it does not canonicalize the path, reject abs ...[truncated 1483 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject absolute attachment paths. 2. Canonicalize the export root and candidate source path before accessing the file. 3. Require the resolved source to remain inside the approved export attachment directory. 4. Require the source to be a regular file and reject symbolic links where appropriate. 5. Optionally allowlist expected attachment extensions and MIME types. 6. Log and skip invalid paths without including unnecessary sensitive path details. Example hardening: ```python export_root = self.flomo_html_dir.resolve() raw_src = Path(attachment["src"]) if raw_src.is_absolute(): logger.warning("Rejected absolute attachment path") continue src_path = (export_root / raw_src).resolve() try: src_path.relative_to(export_root) except ValueError: logger.warning("Rejected attachment path outside export directory") continue if not src_path.is_file() or src_path.is_symlink(): logger.warning("Rejected non-regular attachment") continue ``` If attachments are expected only under a `file` subdirectory, use that directory as the containment root rather than the entire HTML export directory. ]]>
