mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
Any AWS account holder can subscribe their own SNS topic to a Discourse instance's /webhooks/aws and publish bounce notifications that AWS will sign legitimately. The forged bounces are processed against arbitrary user emails, bumping bounce_score and eventually triggering email revocation.. This fix adds a new `aws_sns_topic_arn_allowlist` site setting. Also hardens Jobs::ProcessSnsNotification against three issues: - Binds bounces to (message_id, to_address) via find_by, so a legitimately-subscribed SNS publisher can no longer bounce arbitrary recipients we didn't send to. - Skips duplicate notifications (next if email_log.bounced?) — AWS SNS delivers at-least-once. - Uses update! instead of update_columns so EmailLog's existing before_save normalizes the bounce status code. Also add a dashboard problem flags self-hosted admins whose SMTP looks like SES but who haven't set the allowlist yet. https://github.com/discourse/discourse/security/advisories/GHSA-8f9m-v436-wr3x
43 lines
1.4 KiB
Ruby
Vendored
43 lines
1.4 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module Jobs
|
|
class ProcessSnsNotification < ::Jobs::Base
|
|
sidekiq_options retry: false
|
|
|
|
def execute(args)
|
|
return unless raw = args[:raw].presence
|
|
return unless json = args[:json].presence
|
|
return unless message = json["Message"].presence
|
|
|
|
message =
|
|
begin
|
|
JSON.parse(message)
|
|
rescue JSON::ParserError
|
|
nil
|
|
end
|
|
|
|
return unless message && message["notificationType"] == "Bounce"
|
|
return unless message_id = message.dig("mail", "messageId").presence
|
|
return unless bounce_type = message.dig("bounce", "bounceType").presence
|
|
|
|
return if !Email::Sns.allowed_topic_arn?(json["TopicArn"])
|
|
return unless Email::Sns.authentic?(raw)
|
|
|
|
Array(message.dig("bounce", "bouncedRecipients")).each do |r|
|
|
email_log = EmailLog.find_by(message_id: message_id, to_address: r["emailAddress"])
|
|
next if email_log.nil? || email_log.bounced?
|
|
|
|
email_log.update!(bounced: true, bounce_error_code: r["status"])
|
|
|
|
next if email_log.user&.email.blank?
|
|
|
|
if email_log.user.user_stat.bounce_score.to_s.start_with?("4.") ||
|
|
bounce_type == "Transient"
|
|
Email::Receiver.update_bounce_score(email_log.user.email, SiteSetting.soft_bounce_score)
|
|
else
|
|
Email::Receiver.update_bounce_score(email_log.user.email, SiteSetting.hard_bounce_score)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|