T09 · Insecure Skill Coding Practices
- Location
scripts/weread_to_flomo.py:112- Finding
Unrestricted Webhook Destination Enables Sensitive Note Disclosure and SSRF
- Content
View full analysis
dict: data = urllib.parse.urlencode({'content': content}).encode() req = urllib.request.Request(webhook, data=data, method='POST') with urllib.request.urlopen(req, timeout=20) as resp: return json.loads(resp.read().decode('utf-8', 'ignore')) ``` The destination is accepted directly from a command-line argument: ```python parser.add_argument('--flomo-webhook', required=True) ``` The exported note content is then sent to that destination: ```python result = post_to_flomo(args.flomo_webhook, content) ``` ### Technical Analysis The script treats any value supplied through `--flomo-webhook` as a trusted flomo endpoint. It does not validate: - Whether the URL uses HTTPS. - Whether the hostname belongs to an official flomo service. - Whether the destination resolves to a loopback, link-local, or private address. - Whether an HTTP redirect changes the request destination to an untrusted host. Consequently, the function can transmit private WeRead note content to an attacker-controlled endpoint over HTTP or HTTPS. It can also be used to issue POST requests to services accessible from the machine running the skill, including local or internal-network services. Because the body is URL-encoded and controlled by parsed note content, this does not by itself provide arbitrary code execution. However, it provides an outbound data-disclosure channel and limited server-side request forgery capabilities using the network privileges of the executing host. ### Attack Path 1. An attacker, unsafe wrapper, or misleading instruction provides a URL through `--flomo-webhook`. 2. The user runs the script, potentially with `--mode all`. 3. The script parses all matching Markdown files in the s ...[truncated 1024 chars]- Remediation
View remediation
str: parsed = urlsplit(webhook) if parsed.scheme != "https": raise ValueError("The flomo webhook must use HTTPS") if parsed.hostname not in ALLOWED_WEBHOOK_HOSTS: raise ValueError("Unapproved flomo webhook host") return webhook ``` DNS resolution and redirect validation should be added in addition to this basic hostname check. ]]>
