T09 · Insecure Skill Coding Practices
- Location
bankcardcognition.py:35- Finding
Symlink Bypass Enables Unauthorized Local File Disclosure to a Third-Party API
- Content
View full analysis
Vulnerability Details
File Location:
bankcardcognition.py, lines 35–44, 71–87, and 92–98
Vulnerability Type: Symlink-based path containment bypass and sensitive-file disclosure
Risk Level: HighVulnerable Code
python 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}"}python params = {"appkey": appkey} data = {"pic": pic_base64} try: resp = requests.post(BANKCARD_URL, params=params, data=data, timeout=20)Technical Analysis
The path validator prevents absolute paths and lexical traversal through
.., but it does not resolve symbolic links before determining whether the requested file remains inside the current working directory.Both
os.path.isfile()andopen()follow symbolic links. A relative path that appears to be contained within the working directory can therefore reference a symlink whose target is an arbitrary readable file outside that directory. The target's contents are read in full, base64-encoded, and submitted as thepicfield to the configured JisuAPI endpoint.Encoding the selected bank-card image and sending it to the documented OCR provider are necessary for the Skill's declared cloud OCR functionality. The vulnerability is not the use ...[truncated 1752 chars]
- Remediation
View remediation
Remediation Suggestions
-
Resolve the canonical working directory and candidate path with
os.path.realpath()orpathlib.Path.resolve(). -
Verify canonical containment using
os.path.commonpath()rather than string-prefix checks:python base = os.path.realpath(os.getcwd()) candidate = os.path.realpath(os.path.join(base, norm)) try: if os.path.commonpath([base, candidate]) != base: return { "error": "invalid_path", "message": f"Resolved path escapes the working directory for '{field}'", } except ValueError: return { "error": "invalid_path", "message": f"Invalid path for '{field}'", } -
Reject symbolic links explicitly with
os.path.islink()when symlink support is unnecessary. For stronger protection against check-to-use races on supported systems, open the file withos.open()andO_NOFOLLOW, then read it through the returned descriptor. -
Validate the opened object with
fstat()and ensure that it is a regular file. -
Enforce the documented file-size limit before reading the complete file into memory.
-
Validate the file's actual image signature and permit only expected image formats before upload; do not rely solely on its extension.
-
Recheck containment and file identity at open time to minimize time-of-check/time-of-use race conditions.
-
Run the Skill under a dedicated low-privilege account with access only to the intended upload directory.
-
Clearly inform users that selected images are transferred to a third-party OCR provider and avoid retaining or logging full card numbers or image contents.
-
