T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/contract_ledger.py:462
- Finding
- Unrestricted webhook destination permits SSRF and disclosure of confidential contract metadata## Vulnerability Details **File Location**: `scripts/contract_ledger.py:462-489`, `scripts/contract_ledger.py:505-511`, `scripts/contract_ledger.py:552-554`, and `scripts/contract_ledger.py:631-632` **Vulnerability Type**: Server-Side Request Forgery and sensitive-data disclosure **Risk Level**: High ### Vulnerable Code ```python def send_wecom_webhook(self, message: str, webhook_url: str) -> bool: """ Send a WeCom webhook notification. """ try: import urllib.request payload = json.dumps({ 'msgtype': 'markdown', 'markdown': { 'content': message, }, }).encode('utf-8') req = urllib.request.Request( webhook_url, data=payload, headers={'Content-Type': 'application/json'}, ) with urllib.request.urlopen(req, timeout=10) as resp: result = json.loads(resp.read().decode('utf-8')) if result.get('errcode') == 0: logger.info("WeCom notification succeeded") return True else: logger.warning(f"WeCom notification failed: {result}") return False except Exception as e: logger.error(f"WeCom notification failed: {e}") return False ``` ```python def save_config(self, config: Dict): """Save configuration.""" with open(CONFIG_PATH, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) ``` ```python webhook_url = config.get('webhook_url', '') if webhook_url: ledger.send_wecom_webhook(message, webhook_url) ``` ```python if args.webhook: config['webhook_url'] = args.webhook ``` ### Technical Analysis The webhook URL is accepted from the command line, stored persistently, and later passed directly to `urllib.request.urlopen`. The implementation does not validate ...[truncated 2436 chars]
- Remediation
- ## Remediation Suggestions 1. Restrict webhook destinations to HTTPS. 2. Allowlist the exact official WeCom webhook hostname or hostname suffix required by the feature. 3. Reject URLs containing embedded credentials, fragments, unsupported ports, or malformed hostnames. 4. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified IP ranges for both IPv4 and IPv6. 5. Disable redirects or validate every redirect target using the same security policy. 6. Present the normalized destination and a preview of the transmitted fields before enabling notifications. 7. Require explicit confirmation when changing an existing webhook destination. 8. Store webhook secrets using an operating-system credential store rather than a plaintext JSON file. 9. Add an option to omit or redact contract numbers, counterparties, and amounts from notifications. 10. Log only the destination hostname, never the complete webhook URL because it may contain a secret token. 11. Add automated tests covering localhost, private IP addresses, IPv6 literals, DNS rebinding-resistant resolution, redirects, non-HTTPS schemes, and deceptive hostnames.
