T09 · Insecure Skill Coding Practices
Error
- Location
- idcardrecognition.py:25
- Finding
- Workspace Symlink Bypass Enables Unauthorized File Exfiltration<![CDATA[ ## Vulnerability Details **File Location**: `idcardrecognition.py`, lines 25–45 and 68–100 **Vulnerability Type**: Insufficient canonical-path validation and symlink traversal **Risk Level**: High ### Vulnerable Code ```python def _normalize_local_path(user_path: str, field: str) -> Dict[str, Any]: """ 规范化并限制本地文件路径,只允许在当前工作目录及其子目录内读写。 禁止绝对路径和目录穿越(包含 ..),避免被恶意提示利用读取任意系统文件。 """ if not user_path: return { "error": "invalid_param", "message": f"field '{field}' is empty", } if os.path.isabs(user_path): return { "error": "invalid_path", "message": f"Absolute path is not allowed for '{field}'", } norm = os.path.normpath(user_path) if norm.startswith("..") or norm == "..": return { "error": "invalid_path", "message": f"Path traversal is not allowed for '{field}'", } base = os.getcwd() full = os.path.join(base, norm) return {"error": None, "path": full, "relative": norm} ``` ```python path = safe["path"] if not os.path.isfile(path): return {"pic": None, "error": f"File not found: {safe['relative']}"} try: with open(path, "rb") as f: raw = f.read() except Exception as e: return {"pic": None, "error": f"Failed to read file: {e}"} try: encoded = base64.b64encode(raw).decode("utf-8") except Exception as e: return {"pic": None, "error": f"Failed to base64-encode file: {e}"} return {"pic": encoded, "error": None} ``` ```python params = {"appkey": appkey} data = {"pic": pic_base64, "typeid": typeid} try: resp = requests.post(IDCARD_RECOG_URL, params=params, data=data, timeout=20) except Exception as e: return {"error": "request_failed", "message": str(e)} ``` ### Technical Analysis The path validation rejects absolute paths and lexical parent-direct ...[truncated 2667 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve both the allowed root and candidate path with `os.path.realpath()` or `pathlib.Path.resolve(strict=True)`. 2. Verify canonical containment with `os.path.commonpath()` rather than string-prefix comparisons: ```python root = os.path.realpath(os.getcwd()) candidate = os.path.realpath(os.path.join(root, norm)) if os.path.commonpath([root, candidate]) != root: raise ValueError("Resolved path is outside the allowed directory") ``` 3. Reject symbolic links explicitly with `os.path.islink()` where links are unnecessary. 4. On supported platforms, open files using `os.open()` with `O_NOFOLLOW`, then read from the returned descriptor. Validate the descriptor with `os.fstat()` to reduce time-of-check-to-time-of-use exposure. 5. Apply a maximum input size before reading the entire file into memory. 6. Validate the image signature and supported image format before sending content externally. File extensions alone are insufficient. 7. Run the Skill under a dedicated account with access only to the intended workspace and no access to unrelated secrets. 8. Require explicit user authorization before transmitting identity documents or other sensitive images to the third-party service. ]]>
