fix: [CW-6940] Fix SSRF issue for webhook trigger used by macros and automations (#14155)
This routes external downloads used by webhook fetch used by macros and acutomations through SafeFetch. It closes the SSRF exposure from raw Down.download paths, preserves provider-specific auth and header flows, and adds regression coverage for blocked internal URLs plus authenticated downloads. Fixes # (issue): [CW-6940](https://linear.app/chatwoot/issue/CW-6940/ssrf-via-webhooksautomationmacros-non-upload-non-avatar)
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
class Webhooks::Trigger
|
||||
SUPPORTED_ERROR_HANDLE_EVENTS = %w[message_created message_updated].freeze
|
||||
RETRYABLE_AGENT_BOT_STATUSES = [429, 500].freeze
|
||||
|
||||
class RetryableError < StandardError
|
||||
attr_reader :status
|
||||
|
||||
def initialize(status:, message:)
|
||||
@status = status
|
||||
super(message)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(url, payload, webhook_type, secret: nil, delivery_id: nil)
|
||||
@url = url
|
||||
@@ -15,11 +25,9 @@ class Webhooks::Trigger
|
||||
|
||||
def execute
|
||||
perform_request
|
||||
rescue RestClient::TooManyRequests, RestClient::InternalServerError => e
|
||||
raise if @webhook_type == :agent_bot_webhook
|
||||
|
||||
handle_failure(e)
|
||||
rescue StandardError => e
|
||||
raise RetryableError.new(status: http_status(e), message: e.message) if retryable_agent_bot_error?(e)
|
||||
|
||||
handle_failure(e)
|
||||
end
|
||||
|
||||
@@ -32,17 +40,19 @@ class Webhooks::Trigger
|
||||
|
||||
def perform_request
|
||||
body = @payload.to_json
|
||||
RestClient::Request.execute(
|
||||
SafeFetch.fetch(
|
||||
@url,
|
||||
method: :post,
|
||||
url: @url,
|
||||
payload: body,
|
||||
body: body,
|
||||
headers: request_headers(body),
|
||||
timeout: webhook_timeout
|
||||
)
|
||||
open_timeout: webhook_timeout,
|
||||
read_timeout: webhook_timeout,
|
||||
validate_content_type: false
|
||||
) { |_response| nil }
|
||||
end
|
||||
|
||||
def request_headers(body)
|
||||
headers = { content_type: :json, accept: :json }
|
||||
headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
|
||||
headers['X-Chatwoot-Delivery'] = @delivery_id if @delivery_id.present?
|
||||
if @secret.present?
|
||||
ts = Time.now.to_i.to_s
|
||||
@@ -111,4 +121,14 @@ class Webhooks::Trigger
|
||||
|
||||
timeout&.positive? ? timeout : 5
|
||||
end
|
||||
|
||||
def retryable_agent_bot_error?(error)
|
||||
@webhook_type == :agent_bot_webhook && RETRYABLE_AGENT_BOT_STATUSES.include?(http_status(error))
|
||||
end
|
||||
|
||||
def http_status(error)
|
||||
return unless error.is_a?(SafeFetch::HttpError)
|
||||
|
||||
error.message.to_s[/\A(\d{3})\b/, 1]&.to_i
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user