T05 · Unauthorized Access and Privilege Escalation
- Location
SKILL.md:126- Finding
Unrestricted Reference Image Paths Permit Local File Disclosure
- Content
View full analysis
') ip = json.loads((deck / 'info_pack.json').read_text()) tp = json.loads((deck / 'task_pack.json').read_text()) refs = [p for p in (ip.get('user_assets') or {}).get('reference_images', []) if pathlib.Path(p).exists()] sys_prompt = open('$SKILL_DIR/prompts/style_from_image.md').read() user_prompt = f'PPT 主题/参数: {json.dumps(tp["params"], ensure_ascii=False)}\nuser_query: {ip.get("user_query") or ""}' md = vlm(sys_prompt, user_prompt, images=refs) ``` ### Technical Analysis The reference-image paths are loaded from `info_pack.json` and validated only with `Path.exists()`. The code does not require a path to be located beneath the deck's approved asset directory, does not reject absolute or traversal paths, and does not ensure that the resolved target is a regular image file. As a result, a party able to influence `user_assets.reference_images` can direct the process to any local path readable by the account running the Skill. The selected files are then passed to `vlm(..., images=refs)`, which may read and transmit their contents to the configured VLM service. This violates least privilege because the Skill only needs access to uploaded reference assets, not arbitrary files accessible to the host process. ### Attack Path 1. An attacker gains control over, or supplies data used to create, `/info_pack.json`. 2. The attacker places an absolute path or traversal path in `user_assets.reference_images`, such as a path to a sensitive local image outside the deck directory. 3. `Path(p).exists()` returns true because the target exists. 4. The untrusted path is retained in `refs`. 5. The Skill passes the path to `vlm()` through the `images` argument. 6. The model client reads the file and may transmit its cont ...[truncated 698 chars]- Remediation
View remediation
/assets/reference_images`. 2. Resolve both the trusted root and every supplied path before use. 3. Reject any resolved path that is not contained beneath the trusted root. 4. Require each target to be a regular file and validate its extension and actual media type. 5. Address symlink traversal by resolving the final target before performing the containment check. 6. Reject the entire request, rather than silently accepting unsafe entries, when an out-of-scope path is supplied. 7. Configure the model client to accept uploaded byte streams or validated asset identifiers instead of arbitrary filesystem paths where possible. Example hardening: ```python asset_root = (deck / "assets" / "reference_images").resolve() refs = [] for raw_path in (ip.get("user_assets") or {}).get("reference_images", []): candidate = pathlib.Path(raw_path).expanduser().resolve() if not candidate.is_relative_to(asset_root): raise ValueError(f"Reference image is outside the approved asset directory: {raw_path}") if not candidate.is_file(): raise ValueError(f"Reference image is not a regular file: {raw_path}") if candidate.suffix.lower() not in {".png", ".jpg", ".jpeg", ".webp"}: raise ValueError(f"Unsupported reference image type: {raw_path}") refs.append(str(candidate)) ``` ]]>
