T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/dingbot.py:37
- Finding
- Unrestricted Webhook Destination Enables Disclosure of Local File Contents<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dingbot.py:37-53`, `scripts/dingbot.py:60-61`, and `scripts/dingbot.py:88-91` **Vulnerability Type**: Unvalidated outbound destination combined with arbitrary local file transmission **Risk Level**: Medium ### Vulnerable Code ```python def signed_url() -> str: url = os.environ.get("DING_WEBHOOK") or die("未设置 DING_WEBHOOK") secret = os.environ.get("DING_SECRET") if not secret: return url ts = str(round(time.time() * 1000)) string_to_sign = f"{ts}\n{secret}".encode("utf-8") sign = base64.b64encode( hmac.new(secret.encode("utf-8"), string_to_sign, digestmod=hashlib.sha256).digest()) return f"{url}×tamp={ts}&sign={urllib.parse.quote_plus(sign)}" def send(body: dict) -> None: req = urllib.request.Request( signed_url(), json.dumps(body, ensure_ascii=False).encode("utf-8"), {"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=20) as resp: r = json.loads(resp.read()) ``` ```python def read_arg_or_file(v: str) -> str: return open(v, encoding="utf-8").read() if os.path.isfile(v) else v ``` ```python elif cmd == "markdown": len(rest) >= 2 or die("用法: markdown <标题> <md文件或内容>") send({"msgtype": "markdown", "markdown": {"title": rest[0], "text": read_arg_or_file(rest[1])}}) ``` ### Technical Analysis The documentation states that network requests are limited to `oapi.dingtalk.com`, but the implementation obtains the complete destination from the `DING_WEBHOOK` environment variable and does not validate its scheme, hostname, port, user-information component, or resolved address. The Markdown command treats its second argument as a local path whenever `os.path.isfile()` succeeds. It reads the entire file and places its contents in the outbound JSON message. This is expected when deliberately sending a report file to DingTalk, but the absenc ...[truncated 2587 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse `DING_WEBHOOK` with `urllib.parse.urlsplit()` before creating the request. 2. Require the `https` scheme and require the normalized hostname to be exactly `oapi.dingtalk.com`. Do not use suffix-only checks that could accept domains such as `oapi.dingtalk.com.attacker.example`. 3. Reject embedded usernames or passwords, fragments, malformed URLs, and unexpected ports. 4. Use a redirect handler that either disables redirects or validates every redirect target against the same HTTPS and hostname allowlist. 5. Consider resolving the hostname and rejecting loopback, link-local, private, and reserved destinations if future configuration permits more than one approved hostname. 6. Restrict file-based Markdown input to an explicitly approved workspace or report directory. Resolve paths with `realpath()` and verify that the resulting path remains under that directory. 7. Consider separating literal Markdown content from file input into distinct options, such as `--content` and `--file`, to prevent an argument from unexpectedly being interpreted as a file path. 8. Apply a reasonable file-size limit and reject non-regular files before reading them. 9. Add automated tests covering malicious hostnames, alternate schemes, embedded credentials, nonstandard ports, redirects to unapproved hosts, path traversal, and symbolic links escaping an approved directory. ]]>
