Skip to content

Commit

Permalink
Make external module read loop more robust
Browse files Browse the repository at this point in the history
Changes from a "hope we get at most one message at a time" model to
something beginning to resemble a state machine. Also logs error output
and fails the MSF module when the external module fails.
  • Loading branch information
acammack-r7 committed Nov 20, 2017
1 parent 39f06a3 commit dd57138
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 32 deletions.
27 changes: 16 additions & 11 deletions lib/msf/core/module/external.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

module Msf::Module::External
def wait_status(mod)
while mod.running
m = mod.get_status
if m
case m.method
when :message
log_output(m)
when :report
process_report(m)
when :reply
# we're done
break
begin
while mod.running
m = mod.get_status
if m
case m.method
when :message
log_output(m)
when :report
process_report(m)
when :reply
# we're done
break
end
end
end
rescue Exception => e #Msf::Modules::External::Bridge::Error => e
elog e.backtrace.join("\n")
fail_with Failure::UNKNOWN, e.message
end
end

Expand Down
66 changes: 45 additions & 21 deletions lib/msf/core/modules/external/bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ def initialize(module_path)
self.path = module_path
self.cmd = [self.path, self.path]
self.messages = Queue.new
self.buf = ''
end

protected

attr_writer :path, :running
attr_accessor :cmd, :env, :ios, :messages
attr_accessor :cmd, :env, :ios, :buf, :messages, :wait_thread

def describe
resp = send_receive(Msf::Modules::External::Message.new(:describe))
Expand All @@ -64,8 +65,9 @@ def send_receive(message)
end

def send(message)
input, output, status = ::Open3.popen3(self.env, self.cmd)
self.ios = [input, output, status]
input, output, err, status = ::Open3.popen3(self.env, self.cmd)
self.ios = [input, output, err]
self.wait_thread = status
# We would call Rex::Threadsafe directly, but that would require rex for standalone use
case select(nil, [input], nil, 0.1)
when nil
Expand All @@ -91,33 +93,55 @@ def write_message(fd, json)
end

def recv(filter_id=nil, timeout=600)
_, fd, _ = self.ios
_, out, err = self.ios
message = ''

# Multiple messages can come over the wire all at once, and since yajl
# doesn't play nice with windows, we have to emulate a state machine to
# read just enough off the wire to get one request at a time. Since
# Windows cannot do a nonblocking read on a pipe, we are forced to do a
# whole lot of `select` syscalls :(
buf = ""
# whole lot of `select` syscalls and keep a buffer ourselves :(
begin
loop do
# This is so we don't end up calling JSON.parse on every char and
# catch an exception. Windows can't do nonblock on pipes, so we
# still have to do the select if we are not at the end of object
# and don't have any buffer left
parts = self.buf.split '}', 2
if parts.length == 2 # [part, rest]
message << parts[0] << '}'
self.buf = parts[1]
break
elsif parts.length == 1 # [part]
if self.buf[-1] == '}'
message << parts[0] << '}'
self.buf = ''
break
else
message << parts[0]
self.buf = ''
end
end

# We would call Rex::Threadsafe directly, but that would require Rex for standalone use
case select([fd], nil, nil, timeout)
when nil
res = select([out, err], nil, nil, timeout)
if res == nil
# This is what we would have gotten without Rex and what `readpartial` can also raise
raise EOFError.new
when [[fd], [], []]
c = fd.readpartial(1)
buf << c

# This is so we don't end up calling JSON.parse on every char and
# having to catch an exception. Windows can't do nonblock on pipes,
# so we still have to do the select each time.
break if c == '}'
else
fds = res[0]
# Preferentially drain and log stderr
if fds.include? err
errbuf = err.readpartial(4096)
elog "Unexpected output running #{self.path}:\n#{errbuf}"
end
if fds.include? out
self.buf << out.readpartial(4096)
end
end
end

m = Msf::Modules::External::Message.from_module(JSON.parse(buf))
m = Msf::Modules::External::Message.from_module(JSON.parse(message))
if filter_id && m.id != filter_id
# We are filtering for a response to a particular message, but we got
# something else, store the message and try again
Expand All @@ -128,16 +152,16 @@ def recv(filter_id=nil, timeout=600)
m
end
rescue JSON::ParserError
# Probably an incomplete response, but no way to really tell
# Probably an incomplete response, but no way to really tell. Keep trying
# until EOF
retry
rescue EOFError => e
{}
nil
end
end

def close_ios
input, output, status = self.ios
[input, output].each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
self.ios.each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
end
end

Expand Down
1 change: 1 addition & 0 deletions modules/exploits/linux/smtp/haraka.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def send_mail(to, mailserver, cmd, mfrom, port):
except smtplib.SMTPDataError as err:
if err[0] == 450:
module.log("Triggered bug in target server (%s)"%err[1], 'good')
s.close()
return(True)
module.log("Bug not triggered in target server", 'error')
module.log("it may not be vulnerable or have the attachment plugin activated", 'error')
Expand Down

0 comments on commit dd57138

Please sign in to comment.