Skip to content

Commit 0fb0fab

Browse files
committed
Landing rapid7#9224, robust external module read loop
2 parents 39f06a3 + dd57138 commit 0fb0fab

File tree

3 files changed

+62
-32
lines changed

3 files changed

+62
-32
lines changed

lib/msf/core/module/external.rb

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
module Msf::Module::External
44
def wait_status(mod)
5-
while mod.running
6-
m = mod.get_status
7-
if m
8-
case m.method
9-
when :message
10-
log_output(m)
11-
when :report
12-
process_report(m)
13-
when :reply
14-
# we're done
15-
break
5+
begin
6+
while mod.running
7+
m = mod.get_status
8+
if m
9+
case m.method
10+
when :message
11+
log_output(m)
12+
when :report
13+
process_report(m)
14+
when :reply
15+
# we're done
16+
break
17+
end
1618
end
1719
end
20+
rescue Exception => e #Msf::Modules::External::Bridge::Error => e
21+
elog e.backtrace.join("\n")
22+
fail_with Failure::UNKNOWN, e.message
1823
end
1924
end
2025

lib/msf/core/modules/external/bridge.rb

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ def initialize(module_path)
4343
self.path = module_path
4444
self.cmd = [self.path, self.path]
4545
self.messages = Queue.new
46+
self.buf = ''
4647
end
4748

4849
protected
4950

5051
attr_writer :path, :running
51-
attr_accessor :cmd, :env, :ios, :messages
52+
attr_accessor :cmd, :env, :ios, :buf, :messages, :wait_thread
5253

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

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

9395
def recv(filter_id=nil, timeout=600)
94-
_, fd, _ = self.ios
96+
_, out, err = self.ios
97+
message = ''
9598

9699
# Multiple messages can come over the wire all at once, and since yajl
97100
# doesn't play nice with windows, we have to emulate a state machine to
98101
# read just enough off the wire to get one request at a time. Since
99102
# Windows cannot do a nonblocking read on a pipe, we are forced to do a
100-
# whole lot of `select` syscalls :(
101-
buf = ""
103+
# whole lot of `select` syscalls and keep a buffer ourselves :(
102104
begin
103105
loop do
106+
# This is so we don't end up calling JSON.parse on every char and
107+
# catch an exception. Windows can't do nonblock on pipes, so we
108+
# still have to do the select if we are not at the end of object
109+
# and don't have any buffer left
110+
parts = self.buf.split '}', 2
111+
if parts.length == 2 # [part, rest]
112+
message << parts[0] << '}'
113+
self.buf = parts[1]
114+
break
115+
elsif parts.length == 1 # [part]
116+
if self.buf[-1] == '}'
117+
message << parts[0] << '}'
118+
self.buf = ''
119+
break
120+
else
121+
message << parts[0]
122+
self.buf = ''
123+
end
124+
end
125+
104126
# We would call Rex::Threadsafe directly, but that would require Rex for standalone use
105-
case select([fd], nil, nil, timeout)
106-
when nil
127+
res = select([out, err], nil, nil, timeout)
128+
if res == nil
107129
# This is what we would have gotten without Rex and what `readpartial` can also raise
108130
raise EOFError.new
109-
when [[fd], [], []]
110-
c = fd.readpartial(1)
111-
buf << c
112-
113-
# This is so we don't end up calling JSON.parse on every char and
114-
# having to catch an exception. Windows can't do nonblock on pipes,
115-
# so we still have to do the select each time.
116-
break if c == '}'
131+
else
132+
fds = res[0]
133+
# Preferentially drain and log stderr
134+
if fds.include? err
135+
errbuf = err.readpartial(4096)
136+
elog "Unexpected output running #{self.path}:\n#{errbuf}"
137+
end
138+
if fds.include? out
139+
self.buf << out.readpartial(4096)
140+
end
117141
end
118142
end
119143

120-
m = Msf::Modules::External::Message.from_module(JSON.parse(buf))
144+
m = Msf::Modules::External::Message.from_module(JSON.parse(message))
121145
if filter_id && m.id != filter_id
122146
# We are filtering for a response to a particular message, but we got
123147
# something else, store the message and try again
@@ -128,16 +152,16 @@ def recv(filter_id=nil, timeout=600)
128152
m
129153
end
130154
rescue JSON::ParserError
131-
# Probably an incomplete response, but no way to really tell
155+
# Probably an incomplete response, but no way to really tell. Keep trying
156+
# until EOF
132157
retry
133158
rescue EOFError => e
134-
{}
159+
nil
135160
end
136161
end
137162

138163
def close_ios
139-
input, output, status = self.ios
140-
[input, output].each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
164+
self.ios.each {|fd| fd.close rescue nil} # Yeah, yeah. I know.
141165
end
142166
end
143167

modules/exploits/linux/smtp/haraka.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def send_mail(to, mailserver, cmd, mfrom, port):
7272
except smtplib.SMTPDataError as err:
7373
if err[0] == 450:
7474
module.log("Triggered bug in target server (%s)"%err[1], 'good')
75+
s.close()
7576
return(True)
7677
module.log("Bug not triggered in target server", 'error')
7778
module.log("it may not be vulnerable or have the attachment plugin activated", 'error')

0 commit comments

Comments
 (0)