T09 · Insecure Skill Coding Practices
Warning
- Location
- docs/captcha_strategies.md:110
- Finding
- Unrestricted Local File Upload to an Unspecified CAPTCHA Service<![CDATA[ ## Vulnerability Details **File Location**: `docs/captcha_strategies.md:110-119` and `SKILL.md:339-348` **Vulnerability Type**: Arbitrary readable-file disclosure through an external upload **Risk Level**: Medium ### Vulnerable Code ```python # Use a third-party CAPTCHA recognition API import requests def solve_captcha(image_path): response = requests.post( "https://captcha.service.com/api/solve", files={"image": open(image_path, "rb")}, headers={"Authorization": "Bearer YOUR_API_KEY"} ) return response.json()["solution"] ``` ### Technical Analysis The documented CAPTCHA solver accepts an unrestricted `image_path`, opens that path with the process's current privileges, and transmits its contents to an external service. It does not verify that the resolved path belongs to a dedicated CAPTCHA directory or that the file is actually an image. It also lacks file-size, extension, MIME-type, and symbolic-link validation. Uploading a genuine CAPTCHA image is related to the declared crawler functionality. However, granting the upload routine access to any file readable by the process exceeds the minimum privileges required for that feature. The endpoint is also a placeholder rather than an identified and reviewed provider, so its ownership, retention policy, and data-handling guarantees cannot be established. This is presented as example code rather than an automatically invoked project path. Exploitation therefore requires the example to be adopted or invoked with an attacker-influenced path. ### Attack Path 1. A user integrates or executes the documented `solve_captcha` function. 2. An attacker, untrusted caller, or malformed workflow controls or influences `image_path`. 3. The supplied path points to a sensitive readable file, potentially through a symbolic link or path traversal. 4. `open(image_path, "rb")` reads the file without validation. 5. `requests.post` sends the complete file to the configured external CA ...[truncated 545 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove the placeholder third-party integration from the default workflow. 2. Require explicit user approval before transmitting any image to an external provider. 3. Use an allowlisted, documented HTTPS endpoint whose ownership and retention policy have been reviewed. 4. Restrict uploads to a dedicated CAPTCHA directory: - Resolve the candidate path with `Path.resolve()`. - Confirm that it remains beneath the approved directory. - Reject symbolic links and non-regular files. 5. Validate the file extension, decoded image format, MIME type, and maximum size before transmission. 6. Open files with a context manager so handles are always closed. 7. Keep API credentials outside source files and documentation examples, such as in a protected secret store. 8. Log the destination and file metadata without logging the API token or sensitive file contents. A safer implementation should resemble: ```python from pathlib import Path from PIL import Image import requests CAPTCHA_DIR = Path("screenshots/captcha").resolve() MAX_SIZE = 2 * 1024 * 1024 def solve_captcha(image_path, endpoint, api_key): candidate = Path(image_path).resolve() if CAPTCHA_DIR not in candidate.parents: raise ValueError("CAPTCHA image must be inside the approved directory") if not candidate.is_file() or candidate.is_symlink(): raise ValueError("Invalid CAPTCHA file") if candidate.stat().st_size > MAX_SIZE: raise ValueError("CAPTCHA image is too large") with Image.open(candidate) as image: image.verify() with candidate.open("rb") as image_file: response = requests.post( endpoint, files={"image": (candidate.name, image_file, "image/png")}, headers={"Authorization": f"Bearer {api_key}"}, timeout=15, ) response.raise_for_status() return response.json()["solution"] ``` ]]>
