T05 · Unauthorized Access and Privilege Escalation
- Location
modules/resume-builder/scripts/render.py:32- Finding
Arbitrary Local File Disclosure Through Unrestricted Avatar Paths
- Content
View full analysis
dict: """Resolve avatar path relative to resume.yaml and convert to base64 data URI.""" avatar = (data.get("basics") or {}).get("avatar") if not avatar or avatar.startswith("data:") or avatar.startswith("http"): return data avatar_path = (resume_dir / avatar).resolve() if not avatar_path.is_file(): print(f"⚠️ avatar not found: {avatar_path}", file=sys.stderr) return data mime = mimetypes.guess_type(str(avatar_path))[0] or "image/jpeg" b64 = base64.b64encode(avatar_path.read_bytes()).decode() data["basics"]["avatar"] = f"data:{mime};base64,{b64}" return data ``` The schema accepts any string without path or file-type restrictions: ```json "avatar": {"type": "string", "description": "本地路径或 URL"} ``` ### Technical Analysis The renderer treats the user-controlled `basics.avatar` value as a filesystem path. Although `Path.resolve()` normalizes the path, the resolved result is not checked against an approved base directory. Consequently, both absolute paths and relative traversal sequences such as `../../` can reference any regular file readable by the process. The file contents are read using `read_bytes()`, Base64-encoded, and copied into generated resume output as a data URI. The implementation also does not verify that the selected file is an image. MIME type inference is based only on the filename, and an unrecognized file defaults to `image/jpeg`. This permits arbitrary text or binary files to be embedded. ### Attack Path 1. An attacker supplies or influences a resume YAML file. 2. The attacker sets the avatar field to a sensitive fil ...[truncated 1185 chars]- Remediation
View remediation
