T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/pack-book.py:94
- Finding
- Arbitrary Local File Read and Base64 Disclosure Through Metadata Image Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pack-book.py:94-109, 201-206` **Vulnerability Type**: Path traversal and unrestricted local file read **Risk Level**: High ### Vulnerable Code ```python else: # Read a local file if not os.path.exists(image_path_or_url): raise Exception(f"Image file does not exist: {image_path_or_url}") with open(image_path_or_url, 'rb') as f: image_data = f.read() img_format = image_path_or_url.split('.')[-1].lower() # Validate image data size if len(image_data) < 1000: raise Exception( f"Abnormal image data (only {len(image_data)} bytes): " f"{image_path_or_url}" ) # Convert to Base64 image_base64 = base64.b64encode(image_data).decode('utf-8') ``` The path passed to this function is assembled as follows: ```python if image_source.startswith('http://') or image_source.startswith('https://'): # URL image full_path = image_source else: # Local file full_path = os.path.join(pages_dir, image_source) image_url = image_to_base64(full_path, max_retries) ``` ### Technical Analysis When the local `pages/` directory does not contain image files, image source values are read from attacker-controllable fields in `metadata.json`. A non-HTTP value is treated as a local filename and joined to `pages_dir`. The implementation does not reject: - Absolute paths - `../` path traversal components - Symbolic links that resolve outside `pages_dir` - Files that are not actually images With `os.path.join`, an absolute `image_source` can replace the intended base directory entirely. Relative traversal sequences can also escape from `pages_dir`. The resulting path is passed to `open()` and read with the process's filesystem privileges. Local file content is not passed through `is_valid_image()`. Apart from the minimum size check, arbitrary local data can therefore be Base64-encoded and embedded in the generated HTML as a data URI. The Base64 operation itsel ...[truncated 1582 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject absolute paths from metadata. 2. Canonicalize both the page directory and requested file: ```python base_dir = os.path.realpath(pages_dir) candidate = os.path.realpath(os.path.join(base_dir, image_source)) if os.path.commonpath([base_dir, candidate]) != base_dir: raise ValueError("Image path escapes the pages directory") ``` 3. Reject traversal components before path resolution as defense in depth. 4. Ensure the resolved path is a regular file and handle symbolic links safely. 5. Apply image magic-byte validation to local files as well as downloaded files. 6. Use a strict allowlist of supported image formats rather than accepting unknown binary content. 7. Enforce reasonable maximum input-file sizes to prevent memory exhaustion. 8. Run the packer with a dedicated, low-privilege account that cannot read unrelated secrets. 9. Add tests covering absolute paths, `../` traversal, nested traversal, symbolic links, and non-image files. ]]>
