From 5a632da14da16c7d1f660d0441a0516971055b45 Mon Sep 17 00:00:00 2001 From: Lukas Zapletal Date: Wed, 9 Jun 2021 08:40:34 +0200 Subject: [PATCH] Fixes #32753 - Remote code execution through Sendmail CVE-2021-3584: Sendmail location and arguments, available via Administer - Settings, both accept arbitrary strings and pass them into shell. By default, only Foreman super administrator can access settings. Mitigation: Verify the both settings and remove edit_settings permissions to all roles and users until fixed. Alternatively, create settings named sendmail_location and sendmail_arguments in settings.yaml file to override the UI and make the values read-only. Solution: Limit the possible values for location to just expected paths. Use shellescaping for arguments as there is currently no way to pass arguments to the 'mail' gem in a safely manner. --- app/models/setting/email.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/app/models/setting/email.rb b/app/models/setting/email.rb index c86bab86c..7bf9ef396 100644 --- a/app/models/setting/email.rb +++ b/app/models/setting/email.rb @@ -1,5 +1,12 @@ +require 'shellwords' + class Setting::Email < Setting NON_EMAIL_YAML_SETTINGS = %w(send_welcome_email email_reply_address email_subject_prefix) + SENDMAIL_LOCATIONS = %w(/usr/sbin/sendmail /usr/bin/sendmail /usr/local/sbin/sendmail /usr/local/bin/sendmail) + + def self.sendmail_locations_hash + SENDMAIL_LOCATIONS.zip(SENDMAIL_LOCATIONS).to_h + end def self.default_settings domain = SETTINGS[:domain] @@ -19,19 +26,29 @@ class Setting::Email < Setting set('smtp_password', N_("Password to use to authenticate, if required"), '', N_('SMTP password'), nil, {:encrypted => true}), set('smtp_authentication', N_("Specify authentication type, if required"), '', N_('SMTP authentication'), nil, { :collection => proc { {:plain => "plain", :login => "login", :cram_md5 => "cram_md5", '' => _("none")} }}), set('sendmail_arguments', N_("Specify additional options to sendmail"), '-i', N_('Sendmail arguments')), - set('sendmail_location', N_("The location of the sendmail executable"), "/usr/sbin/sendmail", N_('Sendmail location')), + set('sendmail_location', N_("The location of the sendmail executable"), "/usr/sbin/sendmail", N_('Sendmail location'), nil, { :collection => proc { sendmail_locations_hash } }), ] end validates :value, :length => {:maximum => 255}, :if => proc { |s| s.name == "email_subject_prefix" } + def validate_sendmail_location(record) + if record.value.present? && !SENDMAIL_LOCATIONS.include?(record.value) + record.errors[:base] << _("Not a valid sendmail location") + end + end + def self.delivery_settings options = {} all.find_each do |setting| extracted = {:smtp => extract_prefix(setting.name, 'smtp'), :sendmail => extract_prefix(setting.name, 'sendmail')} ["smtp", "sendmail"].each do |method| if Setting[:delivery_method].to_s == method && setting.name.start_with?(method) && setting.value.to_s.present? - options[extracted[method.to_sym]] = setting.value + if setting.name == "sendmail_arguments" + options[extracted[method.to_sym]] = Shellwords.escape(setting.value) + else + options[extracted[method.to_sym]] = setting.value + end end end end -- 2.31.1