T09 · Insecure Skill Coding Practices
- Location
scripts/contract_ledger.py:462- Finding
Unrestricted webhook destination permits SSRF and disclosure of confidential contract metadata
- Content
View full analysis
Vulnerability Details
File Location:
scripts/contract_ledger.py:462-489,scripts/contract_ledger.py:505-511,scripts/contract_ledger.py:552-554, andscripts/contract_ledger.py:631-632
Vulnerability Type: Server-Side Request Forgery and sensitive-data disclosure
Risk Level: HighVulnerable 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 Falsepython 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.webhookTechnical 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
View remediation
Remediation Suggestions
- Restrict webhook destinations to HTTPS.
- Allowlist the exact official WeCom webhook hostname or hostname suffix required by the feature.
- Reject URLs containing embedded credentials, fragments, unsupported ports, or malformed hostnames.
- Resolve the hostname before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified IP ranges for both IPv4 and IPv6.
- Disable redirects or validate every redirect target using the same security policy.
- Present the normalized destination and a preview of the transmitted fields before enabling notifications.
- Require explicit confirmation when changing an existing webhook destination.
- Store webhook secrets using an operating-system credential store rather than a plaintext JSON file.
- Add an option to omit or redact contract numbers, counterparties, and amounts from notifications.
- Log only the destination hostname, never the complete webhook URL because it may contain a secret token.
- Add automated tests covering localhost, private IP addresses, IPv6 literals, DNS rebinding-resistant resolution, redirects, non-HTTPS schemes, and deceptive hostnames.
