T09 · Insecure Skill Coding Practices
- Location
gmail_enhanced.py:387- Finding
Path Traversal in Attachment Download Enables Arbitrary File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
gmail_enhanced.py, lines 387-393, with the file-write sink at lines 344-365
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: HighVulnerable Code
python def download_attachment( self, msg_id: str, attachment_id: str, save_path: str ) -> Dict: """Download attachment""" service = self._get_service() attachment = service.users().messages().attachments().get( userId='me', messageId=msg_id, id=attachment_id ).execute() # Decode data = base64.urlsafe_b64decode(attachment['data']) # Save with open(save_path, 'wb') as f: f.write(data) return {'saved_to': save_path, 'size': len(data)}python for att in attachments: if att['attachmentId']: save_path = os.path.join(save_dir, att['filename']) self.download_attachment( msg['id'], att['attachmentId'], save_path )Technical Analysis
The attachment filename originates from untrusted MIME metadata supplied by the email sender. The application directly combines that filename with
save_dirusingos.path.join()without validating or normalizing it.A filename containing traversal components such as
../../.bashrccan escape the intended download directory. If the attachment filename is an absolute path,os.path.join()can also discardsave_direntirely. The resulting path reachesopen(save_path, 'wb'), which creates or truncates the destination file.No resolved-path containment check, basename normalization, absolute-path rejection, symlink protection, or overwrite prevention is applied.
Attack Path
- An attacker sends an email containing an attachment whose MIME filename is a traversal path, such as
../../.bashrc. - The victim invokes `search_att ...[truncated 1254 chars]
- An attacker sends an email containing an attachment whose MIME filename is a traversal path, such as
- Remediation
View remediation
Remediation Suggestions
Treat every attachment filename as untrusted input:
- Reject absolute paths and filenames containing traversal components.
- Reduce the supplied filename to a safe basename and replace unsafe characters.
- Resolve both the download directory and destination path, then verify that the destination remains inside the download directory.
- Generate a server-controlled, collision-resistant filename rather than relying on MIME metadata.
- Use exclusive file creation where replacement is not required.
- Reject symlink destinations and consider opening files with platform-supported no-follow protections.
- Preserve the original filename only as display metadata.
Example hardening pattern:
python from pathlib import Path import re import uuid base_dir = Path(save_dir).resolve() base_dir.mkdir(parents=True, exist_ok=True) supplied_name = Path(att['filename']).name safe_name = re.sub(r'[^A-Za-z0-9._-]', '_', supplied_name) safe_name = safe_name or f"attachment-{uuid.uuid4().hex}" destination = (base_dir / safe_name).resolve() if destination.parent != base_dir: raise ValueError("Unsafe attachment path") with open(destination, 'xb') as f: f.write(data)If subdirectories are intentionally supported, use
destination.is_relative_to(base_dir)or an equivalent containment check rather than requiring the immediate parent to equalbase_dir.
