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
-
#banner ⇒ Object
The banner received after the initial connection to the server.
Attributes included from Tcp
Instance Method Summary collapse
- #bad_address(address) ⇒ Object
-
#connect(global = true) ⇒ Object
Establish an SMTP connection to host and port specified by the RHOST and RPORT options, respectively.
- #connect_ehlo(global = true, domain) ⇒ Object
-
#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
andPASSWORD
datastore options). - #connected? ⇒ Boolean
- #disconnect(nsock = self.sock) ⇒ Object
- #generate_ssl_context(security = :high) ⇒ Object protected
-
#initialize(info = {}) ⇒ Object
Creates an instance of an exploit that delivers messages via SMTP.
-
#send_message(data) ⇒ Object
Sends an email message, connecting to the server first if a connection is not already established.
-
#smtp_send_recv(cmd, nsock = self.sock) ⇒ Object
Send and receive a single command using SMTP protocol allowing for response continuation.
-
#swap_sock_plain_to_ssl(nsock = self.sock, security = :high) ⇒ Object
protected
Create a new SSL session on the existing socket.
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
#banner ⇒ Object
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 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. = 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 connect_login(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
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 ( [ 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 (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 = connect_login(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.) if already_connected print_error("Closing and reconnecting...") disconnect(nsock) connect_login 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 |