Module: Msf::Exploit::Remote::SMTPDeliver

Includes:
Tcp
Defined in:
lib/msf/core/exploit/remote/smtp_deliver.rb

Overview

This module exposes methods that may be useful to exploits that send email messages via SMTP.

Defined Under Namespace

Classes: SMTPCommunicationError

Instance Attribute Summary collapse

Attributes included from Tcp

#sock

Instance Method Summary collapse

Methods included from Tcp

#chost, #cleanup, #connect_timeout, #cport, #handler, #lhost, #lport, #peer, #print_prefix, #proxies, #rhost, #rport, #set_tcp_evasions, #shutdown, #ssl, #ssl_cipher, #ssl_verify_mode, #ssl_version

Instance Attribute Details

The banner received after the initial connection to the server. This should look something like:

220 mx.google.com ESMTP s5sm3837150wak.12


282
283
284
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 282

def banner
  @banner
end

Instance Method Details

#bad_address(address) ⇒ Object



153
154
155
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 153

def bad_address(address)
  address.bytesize > 2048 || /[\r\n]/ =~ address
end

#connect(global = true) ⇒ Object

Establish an SMTP connection to host and port specified by the RHOST and RPORT options, respectively. After connecting, the banner message is read in and stored in the banner attribute.

This method does NOT perform an EHLO, it only connects.



54
55
56
57
58
59
60
61
62
63
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 54

def connect(global = true)
  fd = super

  if fd
    @connected = true
    # Wait for a banner to arrive...
    self.banner = fd.get_once(-1, 30)
  end
  fd
end

#connect_ehlo(global = true, domain) ⇒ Object



146
147
148
149
150
151
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 146

def connect_ehlo(global = true, domain)
  vprint_status("Connecting to SMTP server #{rhost}:#{rport}...")
  nsock = connect(global)

  [nsock, smtp_send_recv("EHLO #{domain}\r\n", nsock)]
end

#connect_login(global = true) ⇒ Object

Connect to the remote SMTP server, send EHLO, start TLS if the server asks for it, and authenticate if we’ve got creds (specified in USERNAME and PASSWORD datastore options).

This method currently only knows about PLAIN authentication.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 72

def (global = true)
  if datastore['DOMAIN'] && datastore['DOMAIN'] != ''
    domain = datastore['DOMAIN']
  else
    domain = Rex::Text.rand_text_alpha(rand(32)+1)
  end

  nsock, res = connect_ehlo(global, domain)

  if res =~ /STARTTLS/
    print_status("Starting tls")
    smtp_send_recv("STARTTLS\r\n", nsock)

    [:high, :medium, :default].each do |level|
      begin
        swap_sock_plain_to_ssl(nsock, level)
        break
      rescue OpenSSL::SSL::SSLError
        # Perform manual fallback for servers that can't
        print_status 'Could not negotiate SSL, falling back to older ciphers'
        nsock.close
        nsock, res = connect_ehlo(global)
        smtp_send_recv("STARTTLS\r\n", nsock)
        raise if level == :default
      end
    end

    res = smtp_send_recv("EHLO #{domain}\r\n", nsock)
  end

  unless datastore['PASSWORD'].empty? and datastore["USERNAME"].empty?
    # TODO: other auth methods
    if res =~ /AUTH .*PLAIN/
      if datastore["USERNAME"] and not datastore["USERNAME"].empty?
        # Have to double the username.  SMTP auth is weird
        user = "#{datastore["USERNAME"]}\0" * 2
        auth = Rex::Text.encode_base64("#{user}#{datastore["PASSWORD"]}")
        res = smtp_send_recv("AUTH PLAIN #{auth}\r\n", nsock)
        unless res[0..2] == '235'
          print_error("Authentication failed, quitting")
          disconnect(nsock)
          raise 'Could not authenticate to SMTP server'
        end
      else
        print_status("Server requested auth and no creds given, trying to continue anyway")
      end
    elsif res =~ /AUTH .*LOGIN/
      if datastore["USERNAME"] and not datastore["USERNAME"].empty?
        user = Rex::Text.encode_base64("#{datastore["USERNAME"]}")
        auth = Rex::Text.encode_base64("#{datastore["PASSWORD"]}")
        smtp_send_recv("AUTH LOGIN\r\n", nsock)
        smtp_send_recv("#{user}\r\n", nsock)
        res = smtp_send_recv("#{auth}\r\n", nsock)
        unless res[0..2] == '235'
          print_error("Authentication failed, quitting")
          disconnect(nsock)
          raise 'Could not authenticate to SMTP server'
        end
      else
        print_status("Server requested auth and no creds given, trying to continue anyway")
      end
    elsif res =~ /AUTH/
      print_error("Server doesn't accept any supported authentication, trying to continue anyway")
    else
      if datastore['PASSWORD'] and datastore["USERNAME"] and not datastore["USERNAME"].empty?
        # Let the user know their creds are going unused
        vprint_status("Server didn't ask for authentication, skipping")
      end
    end
  end

  return nsock
end

#connected?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 43

def connected?
  (@connected)
end

#disconnect(nsock = self.sock) ⇒ Object



235
236
237
238
239
240
241
242
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 235

def disconnect(nsock=self.sock)
  begin
    smtp_send_recv("QUIT\r\n", nsock)
  rescue SMTPCommunicationError => _e
  end
  super
  @connected = false
end

#generate_ssl_context(security = :high) ⇒ Object (protected)



302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 302

def generate_ssl_context(security=:high)
  case security
  when :high
    ctx = OpenSSL::SSL::SSLContext.new(:SSLv23)
    ctx.ciphers = "ALL:!ADH:!EXPORT:!SSLv2:!SSLv3:+HIGH:+MEDIUM"
    ctx
  when :medium
    OpenSSL::SSL::SSLContext.new(:TLSv1)
  when :default
    OpenSSL::SSL::SSLContext.new
  end
end

#initialize(info = {}) ⇒ Object

Creates an instance of an exploit that delivers messages via SMTP



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 20

def initialize(info = {})
  super

  # Register our options, overriding the RHOST/RPORT from TCP
  register_options(
    [
      OptAddress.new("RHOST", [ true, "The SMTP server to send through" ]),
      OptPort.new("RPORT", [ true, "The SMTP server port (e.g. 25, 465, 587, 2525)", 25 ]),
      OptString.new('DATE', [false, 'Override the DATE: field with this value', '']),
      OptString.new('MAILFROM', [ true, 'The FROM address of the e-mail', 'random@example.com' ]),
      OptString.new('MAILTO', [ true, 'The TO address of the email' ]),
      OptString.new('SUBJECT', [ true, 'Subject line of the email' ]),
      OptString.new('USERNAME', [ false, 'SMTP Username for sending email', '' ]),
      OptString.new('PASSWORD', [ false, 'SMTP Password for sending email', '' ]),
      OptString.new('DOMAIN', [false, 'SMTP Domain to EHLO to', '']),
      OptString.new('VERBOSE', [ false, 'Display verbose information' ]),
    ], Msf::Exploit::Remote::SMTPDeliver)
  register_autofilter_ports([ 25, 465, 587, 2525, 25025, 25000])
  register_autofilter_services(%W{ smtp smtps })

  @connected = false
end

#send_message(data) ⇒ Object

Sends an email message, connecting to the server first if a connection is not already established.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 161

def send_message(data)
  mailfrom = datastore['MAILFROM'].strip
  if bad_address(mailfrom)
    print_error "Bad from address, not sending: #{mailfrom}"
    return nil
  end

  mailto = datastore['MAILTO'].strip
  if bad_address(mailto)
    print_error "Bad to address, not sending: #{mailto}"
    return nil
  end

  send_status = nil

  already_connected = connected?
  if already_connected
    print_status("Already connected, reusing")
    nsock = self.sock
  else
    nsock = (false)
  end

  smtp_send_recv("MAIL FROM: <#{mailfrom}>\r\n", nsock)
  res = smtp_send_recv("RCPT TO: <#{mailto}>\r\n", nsock)
  if res && res[0..2] == '250'
    resp = smtp_send_recv("DATA\r\n", nsock)

    # If the user supplied a Date field, use that, else use the current
    # DateTime in the proper RFC2822 format.
    if datastore['DATE'].present?
      date = "Date: #{datastore['DATE']}\r\n"
    else
      date = "Date: #{DateTime.now.rfc2822}\r\n"
    end

    # If the user supplied a Subject field, use that
    subject = nil
    if datastore['SUBJECT'].present?
      subject = "Subject: #{datastore['SUBJECT']}\r\n"
    end

    # Avoid sending tons of data and killing the connection if the server
    # didn't like us.
    if not resp or not resp[0,3] == '354'
      print_error("Server refused our mail")
    else
      full_msg = ''
      full_msg << date unless data =~ /date: /i
      full_msg << subject unless subject.nil? || data =~ /subject: /i
      full_msg << data
      # Escape leading dots in the mail messages so there are no false EOF
      full_msg.gsub!(/(?m)^\./, '..')
      send_status = smtp_send_recv("#{full_msg}\r\n.\r\n", nsock)
    end
  else
    print_error "Server refused to send to <#{mailto}>"
  end

  if not already_connected
    vprint_status("Closing the connection...")
    disconnect(nsock)
  end

  send_status
rescue SMTPCommunicationError => e
  print_error(e.message)
  if already_connected
    print_error("Closing and reconnecting...")
    disconnect(nsock)
    
  end
end

#smtp_send_recv(cmd, nsock = self.sock) ⇒ Object

Send and receive a single command using SMTP protocol allowing for response continuation



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 246

def smtp_send_recv(cmd, nsock=self.sock)
  return false if not nsock
  if cmd =~ /AUTH PLAIN/
    # Don't print the user's plaintext password
    vprint_status("C: AUTH PLAIN ...")
  else
    # Truncate because this will include a full email and we don't want
    # to dump it all.
    vprint_status("C: #{((cmd.length > 120) ? cmd[0,120] + "..." : cmd).strip}")
  end
  begin
    nsock.put(cmd)
    res = nsock.get_once
    while !(res =~ /(^|\r\n)\d{3}( .*|)\r\n$/)
      chunk = nsock.get_once
      break unless chunk
      if res
        res += chunk
      else
        res = chunk
      end
    end
    raise SMTPCommunicationError.new("SMTP response is incomplete or contains extra data") unless res =~ /(^|\r\n)\d{3}( .*|)\r\n$/
  rescue EOFError
    return nil
  end
  # Don't truncate the server output because it might be helpful for
  # debugging.
  vprint_status("S: #{res.strip}") if res

  return res
end

#swap_sock_plain_to_ssl(nsock = self.sock, security = :high) ⇒ Object (protected)

Create a new SSL session on the existing socket. Used for STARTTLS support.



291
292
293
294
295
296
297
298
299
300
# File 'lib/msf/core/exploit/remote/smtp_deliver.rb', line 291

def swap_sock_plain_to_ssl(nsock=self.sock, security=:high)
  ctx = generate_ssl_context(security)
  ssl = OpenSSL::SSL::SSLSocket.new(nsock, ctx)

  ssl.connect

  nsock.extend(Rex::Socket::SslTcp)
  nsock.sslsock = ssl
  nsock.sslctx  = ctx
end