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

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

Overview

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

Instance Attribute Summary collapse

Attributes included from Tcp

#sock

Instance Method Summary collapse

Methods included from Tcp

#chost, #cleanup, #connect_timeout, #cport, #deregister_tcp_options, #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

261
262
263
# File 'lib/msf/core/exploit/smtp_deliver.rb', line 261

def banner
  @banner
end

Instance Method Details

#bad_address(address) ⇒ Object


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

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.


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

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


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

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

  [nsock, raw_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.


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
145
# File 'lib/msf/core/exploit/smtp_deliver.rb', line 73

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")
    raw_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)
        raw_send_recv("STARTTLS\r\n", nsock)
        raise if level == :default
      end
    end

    res = raw_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 = raw_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"]}")
        raw_send_recv("AUTH LOGIN\r\n",nsock)
        raw_send_recv("#{user}\r\n",nsock)
        res = raw_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)

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

def connected?
  (@connected)
end

#disconnect(nsock = self.sock) ⇒ Object


229
230
231
232
233
# File 'lib/msf/core/exploit/smtp_deliver.rb', line 229

def disconnect(nsock=self.sock)
  raw_send_recv("QUIT\r\n", nsock)
  super
  @connected = false
end

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


281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/msf/core/exploit/smtp_deliver.rb', line 281

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


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

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', '[email protected]' ]),
      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

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


235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/msf/core/exploit/smtp_deliver.rb', line 235

def raw_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
  rescue
    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

#send_message(data) ⇒ Object

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


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
# File 'lib/msf/core/exploit/smtp_deliver.rb', line 162

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

  raw_send_recv("MAIL FROM: <#{mailfrom}>\r\n", nsock)
  res = raw_send_recv("RCPT TO: <#{mailto}>\r\n", nsock)
  if res && res[0..2] == '250'
    resp = raw_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 = raw_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
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.


270
271
272
273
274
275
276
277
278
279
# File 'lib/msf/core/exploit/smtp_deliver.rb', line 270

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