Permalink
Browse files

Fix security vulnerability allowing command line exploit when using e…

…xim or sendmail from the command line
  • Loading branch information...
mikel committed Mar 6, 2012
1 parent 47e288e commit ac56f03bdfc30b379aeecd4ff317d08fdaa328c2
@@ -29,7 +29,7 @@ module Mail # :doc:
require 'mail/core_extensions/nil'
require 'mail/core_extensions/object'
require 'mail/core_extensions/string'
require 'mail/core_extensions/shellwords' unless String.new.respond_to?(:shellescape)
require 'mail/core_extensions/shell_escape'
require 'mail/core_extensions/smtp' if RUBY_VERSION < '1.9.3'
require 'mail/indifferent_hash'
@@ -0,0 +1,56 @@
# encoding: utf-8
# The following is an adaptation of ruby 1.9.2's shellwords.rb file,
# it is modified to include '+' in the allowed list to allow for
# sendmail to accept email addresses as the sender with a + in them
#
module Mail
module ShellEscape
# Escapes a string so that it can be safely used in a Bourne shell
# command line.
#
# Note that a resulted string should be used unquoted and is not
# intended for use in double quotes nor in single quotes.
#
# open("| grep #{Shellwords.escape(pattern)} file") { |pipe|
# # ...
# }
#
# +String#shellescape+ is a shorthand for this function.
#
# open("| grep #{pattern.shellescape} file") { |pipe|
# # ...
# }
#
def escape_for_shell(str)
# An empty argument will be skipped, so return empty quotes.
return "''" if str.empty?
str = str.dup
# Process as a single byte sequence because not all shell
# implementations are multibyte aware.
str.gsub!(/([^A-Za-z0-9_\s\+\-.,:\/@\n])/n, "\\\\\\1")
# A LF cannot be escaped with a backslash because a backslash + LF
# combo is regarded as line continuation and simply ignored.
str.gsub!(/\n/, "'\n'")
return str
end
module_function :escape_for_shell
end
end
class String
# call-seq:
# str.shellescape => string
#
# Escapes +str+ so that it can be safely used in a Bourne shell
# command line. See +Shellwords::shellescape+ for details.
#
def escape_for_shell
Mail::ShellEscape.escape_for_shell(self)
end
end

This file was deleted.

Oops, something went wrong.
@@ -1,12 +1,45 @@
module Mail
# A delivery method implementation which sends via exim.
#
# To use this, first find out where the exim binary is on your computer,
# if you are on a mac or unix box, it is usually in /usr/sbin/exim, this will
# be your exim location.
#
# Mail.defaults do
# delivery_method :exim
# end
#
# Or if your exim binary is not at '/usr/sbin/exim'
#
# Mail.defaults do
# delivery_method :exim, :location => '/absolute/path/to/your/exim'
# end
#
# Then just deliver the email as normal:
#
# Mail.deliver do
# to 'mikel@test.lindsaar.net'
# from 'ada@test.lindsaar.net'
# subject 'testing exim'
# body 'testing exim'
# end
#
# Or by calling deliver on a Mail message
#
# mail = Mail.new do
# to 'mikel@test.lindsaar.net'
# from 'ada@test.lindsaar.net'
# subject 'testing exim'
# body 'testing exim'
# end
#
# mail.deliver!
class Exim < Sendmail
def deliver!(mail)
envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
return_path = "-f \"#{envelope_from.to_s.shellescape}\"" if envelope_from
arguments = [settings[:arguments], return_path].compact.join(" ")
self.class.call(settings[:location], arguments, mail)
def initialize(values)
self.settings = { :location => '/usr/sbin/exim',
:arguments => '-i -t' }.merge(values)
end
def self.call(path, arguments, mail)
@@ -45,14 +45,14 @@ def initialize(values)
def deliver!(mail)
envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
return_path = "-f \"#{envelope_from.to_s.gsub('"', '\"')}\"" if envelope_from
return_path = "-f " + '"' + envelope_from.escape_for_shell + '"' if envelope_from
arguments = [settings[:arguments], return_path].compact.join(" ")
Sendmail.call(settings[:location], arguments, mail.destinations.collect(&:shellescape).join(" "), mail)
self.class.call(settings[:location], arguments, mail.destinations.collect(&:shellescape).join(" "), mail)
end
def Sendmail.call(path, arguments, destinations, mail)
def self.call(path, arguments, destinations, mail)
IO.popen("#{path} #{arguments} #{destinations}", "w+") do |io|
io.puts mail.encoded.to_lf
io.flush
@@ -0,0 +1,161 @@
# encoding: utf-8
require 'spec_helper'
describe "exim delivery agent" do
before(:each) do
# Reset all defaults back to original state
Mail.defaults do
delivery_method :smtp, { :address => "localhost",
:port => 25,
:domain => 'localhost.localdomain',
:user_name => nil,
:password => nil,
:authentication => nil,
:enable_starttls_auto => true }
end
end
it "should send an email using exim" do
Mail.defaults do
delivery_method :exim
end
mail = Mail.new do
from 'roger@test.lindsaar.net'
to 'marcel@test.lindsaar.net, bob@test.lindsaar.net'
subject 'invalid RFC2822'
end
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "roger@test.lindsaar.net"',
'marcel@test.lindsaar.net bob@test.lindsaar.net',
mail)
mail.deliver!
end
describe "return path" do
it "should send an email with a return-path using exim" do
Mail.defaults do
delivery_method :exim
end
mail = Mail.new do
to "to@test.lindsaar.net"
from "from@test.lindsaar.net"
sender "sender@test.lindsaar.net"
subject "Can't set the return-path"
return_path "return@test.lindsaar.net"
message_id "<1234@test.lindsaar.net>"
body "body"
end
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "return@test.lindsaar.net"',
'to@test.lindsaar.net',
mail)
mail.deliver
end
it "should use the sender address is no return path is specified" do
Mail.defaults do
delivery_method :exim
end
mail = Mail.new do
to "to@test.lindsaar.net"
from "from@test.lindsaar.net"
sender "sender@test.lindsaar.net"
subject "Can't set the return-path"
message_id "<1234@test.lindsaar.net>"
body "body"
end
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "sender@test.lindsaar.net"',
'to@test.lindsaar.net',
mail)
mail.deliver
end
it "should use the from address is no return path or sender are specified" do
Mail.defaults do
delivery_method :exim
end
mail = Mail.new do
to "to@test.lindsaar.net"
from "from@test.lindsaar.net"
subject "Can't set the return-path"
message_id "<1234@test.lindsaar.net>"
body "body"
end
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "from@test.lindsaar.net"',
'to@test.lindsaar.net',
mail)
mail.deliver
end
it "should escape the return path address" do
Mail.defaults do
delivery_method :exim
end
mail = Mail.new do
to 'to@test.lindsaar.net'
from '"from+suffix test"@test.lindsaar.net'
subject 'Can\'t set the return-path'
message_id '<1234@test.lindsaar.net>'
body 'body'
end
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "\"from+suffix test\"@test.lindsaar.net"',
'to@test.lindsaar.net',
mail)
mail.deliver
end
end
it "should still send an email if the settings have been set to nil" do
Mail.defaults do
delivery_method :exim, :arguments => nil
end
mail = Mail.new do
from 'from@test.lindsaar.net'
to 'marcel@test.lindsaar.net, bob@test.lindsaar.net'
subject 'invalid RFC2822'
end
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-f "from@test.lindsaar.net"',
'marcel@test.lindsaar.net bob@test.lindsaar.net',
mail)
mail.deliver!
end
it "should escape evil haxxor attemptes" do
Mail.defaults do
delivery_method :exim, :arguments => nil
end
mail = Mail.new do
from '"foo\";touch /tmp/PWNED;\""@blah.com'
to 'marcel@test.lindsaar.net'
subject 'invalid RFC2822'
end
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
"-f \"\\\"foo\\\\\\\"\\;touch /tmp/PWNED\\;\\\\\\\"\\\"@blah.com\"",
'marcel@test.lindsaar.net',
mail)
mail.deliver!
end
end
@@ -123,7 +123,6 @@
end
end
it "should still send an email if the settings have been set to nil" do
Mail.defaults do
delivery_method :sendmail, :arguments => nil
@@ -141,4 +140,22 @@
mail)
mail.deliver!
end
it "should escape evil haxxor attemptes" do
Mail.defaults do
delivery_method :sendmail, :arguments => nil
end
mail = Mail.new do
from '"foo\";touch /tmp/PWNED;\""@blah.com'
to 'marcel@test.lindsaar.net'
subject 'invalid RFC2822'
end
Mail::Sendmail.should_receive(:call).with('/usr/sbin/sendmail',
"-f \"\\\"foo\\\\\\\"\\;touch /tmp/PWNED\\;\\\\\\\"\\\"@blah.com\"",
'marcel@test.lindsaar.net',
mail)
mail.deliver!
end
end

0 comments on commit ac56f03

Please sign in to comment.