T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- aliyun_oss_uploader.py:84
- Finding
- Unrestricted Local File Upload Allows Sensitive Host Data Exposure<![CDATA[ ## Vulnerability Details **File Location**: `main.py:29-33`, `handle_media.py:14-29`, `aliyun_oss_uploader.py:84-133`, `security_validator.py:79-109` **Vulnerability Type**: Missing path authorization and disconnected security validation **Risk Level**: High ### Vulnerable Code ```python # main.py:29-33 from aliyun_oss_uploader import AliyunOSSUploader uploader = AliyunOSSUploader() file_path = sys.argv[2] prefix = sys.argv[3] if len(sys.argv) > 3 else "" result = uploader.upload_single_file(file_path, oss_key=prefix if prefix else None) ``` ```python # handle_media.py:14-29 def handle_media(file_path: str) -> str: """ OpenClaw standard media processing interface. """ try: from aliyun_oss_uploader import AliyunOSSUploader uploader = AliyunOSSUploader() # Upload file result = uploader.upload_single_file(file_path) ``` ```python # aliyun_oss_uploader.py:84-133 def upload_single_file( self, local_file: str, oss_key: Optional[str] = None, rename_strategy: str = "uuid", public_read: bool = False ) -> Dict[str, str]: # Validate that the file exists if not os.path.exists(local_file): raise FileNotFoundError(f"File does not exist: {local_file}") # Validate file size if not self.validate_file_size(local_file): raise ValueError(f"File exceeds size limit: {local_file}") # Generate OSS key if oss_key is None: filename = os.path.basename(local_file) unique_filename = self.generate_unique_filename(filename, rename_strategy) oss_key = unique_filename elif '/' not in oss_key and not oss_key.startswith('/'): prefix = self.config.get('default_prefix', '') if prefix: oss_key = f"{prefix.rstrip('/')}/{oss_key}" file_size = os.path.getsize(local_file) large_file_threshold = self.config.get( 'large_file_threshold_mb', 100 ) * 1024 * 1024 try: if file_size > large_file_thre ...[truncated 3085 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Enforce authorization in the upload sink** - Integrate validation directly into `upload_single_file()` so no CLI, media handler, or future caller can bypass it. - Do not rely exclusively on checks in entry-point code. 2. **Restrict uploads to approved roots** - Define explicit media or workspace directories in configuration. - Resolve the requested path with `Path.resolve(strict=True)`. - Verify that the canonical path is a descendant of an approved root using `Path.is_relative_to()` or an equivalent safe comparison. 3. **Require regular files** - Reject symbolic links and non-regular filesystem objects. - Use `os.lstat()` and `stat.S_ISREG()` to reject devices, sockets, FIFOs, and directories. - Where practical, open files with protections such as `O_NOFOLLOW` and validate the opened descriptor to reduce time-of-check/time-of-use races. 4. **Apply file policy controls** - Enforce configured extension rules. - Validate content using trusted MIME detection or file signatures rather than extensions alone. - Retain size limits and introduce conservative defaults for Agent-triggered uploads. 5. **Protect sensitive paths** - Explicitly deny the OpenClaw configuration directory, credential stores, SSH directories, environment files, and other secret-bearing locations. - Treat deny lists only as defense in depth; an approved-root policy should remain the primary control. 6. **Add user authorization** - Require explicit confirmation before uploading paths not supplied as genuine media attachments. - Display the canonical local path, destination bucket, object key, and intended ACL before upload. 7. **Reduce process privileges** - Run the Skill under a dedicated non-root account with access only to the required media directory. - Mount secret directories and unrelated user data as inaccessible to the uploader process. 8. **Add regression tests** - Confirm rejection of `../ ...[truncated 125 chars]
