-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
fix: prevent path traversal in backup importer (CWE-22) #7681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
41e9200
0f135d6
59884be
c64d8cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -59,6 +59,20 @@ def _get_major_version(version_str: str) -> str: | |||||||||||||||||||||||||||||||||||||||||||||||||||
| return "0.0" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _validate_path_within(target_path: Path, base_dir: Path) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Validate that target_path is within base_dir after resolving symlinks. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Prevents path traversal attacks (CWE-22) by ensuring the resolved | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| target path is relative to the resolved base directory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolved = target_path.resolve(strict=False) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| base_resolved = base_dir.resolve(strict=False) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return resolved.is_relative_to(base_resolved) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| except (OSError, ValueError): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+68
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 suggestion (security): Broaden exception handling here to cover potential symlink loop errors
Suggested implementation: try:
resolved = target_path.resolve(strict=False)
base_resolved = base_dir.resolve(strict=False)
return resolved.is_relative_to(base_resolved)
except (OSError, ValueError, RuntimeError):
return False |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of Additionally, note that
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| CMD_CONFIG_FILE_PATH = os.path.join(get_astrbot_data_path(), "cmd_config.json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| KB_PATH = get_astrbot_knowledge_base_path() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| DEFAULT_PLATFORM_STATS_INVALID_COUNT_WARN_LIMIT = 5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -765,6 +779,10 @@ async def _import_knowledge_bases( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| rel_path = name[len(media_prefix) :] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_path = kb_dir / rel_path | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate path is within kb directory (CWE-22) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not _validate_path_within(target_path, kb_dir): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning(f"媒体文件路径越界,已跳过: {target_path}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_path.parent.mkdir(parents=True, exist_ok=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| with zf.open(name) as src, open(target_path, "wb") as dst: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| dst.write(src.read()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -827,6 +845,11 @@ async def _import_attachments( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_path = attachments_dir / os.path.basename(name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate path is within attachments directory (CWE-22) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not _validate_path_within(target_path, attachments_dir): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning(f"附件路径越界,已跳过: {target_path}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_path.parent.mkdir(parents=True, exist_ok=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| with zf.open(name) as src, open(target_path, "wb") as dst: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| dst.write(src.read()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -904,6 +927,10 @@ async def _import_directories( | |||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_path = target_dir / rel_path | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate path is within target directory (CWE-22) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not _validate_path_within(target_path, target_dir): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| result.add_warning(f"文件路径越界,已跳过: {name}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_path.parent.mkdir(parents=True, exist_ok=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| with zf.open(name) as src, open(target_path, "wb") as dst: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Consider Python version compatibility and explicit
strictbehavior in_validate_path_within.Path.is_relative_towas added in Python 3.9; if we still support 3.8 or earlier, this will raise at runtime. Also,Path.resolve()currently relies on the implicitstrict=Falsedefault—please passstrict=Falseexplicitly so the behavior is clear and stable if the default ever changes.