Skip to content

Commit 3595a23

Browse files
committed
Restore rapid7#3738
1 parent 3d30cef commit 3595a23

File tree

1 file changed

+90
-5
lines changed

1 file changed

+90
-5
lines changed

lib/msf/core/exploit/smb/server.rb

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,18 @@ def initialize(info = {})
1818
deregister_options('SSL', 'SSLCert')
1919
register_options(
2020
[
21-
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ])
21+
OptPort.new('SRVPORT', [ true, 'The local port to listen on.', 445 ])
2222
], self.class)
23+
24+
register_advanced_options(
25+
[
26+
OptInt.new('SMBServerMaximumBuffer', [ true, 'The maximum number of data in megabytes to buffer', 2 ]),
27+
OptInt.new('SMBServerIdleTimeout', [ true, 'The maximum amount of time to keep an idle session open in seconds', 120 ])
28+
], self.class)
29+
30+
@smb_server_last_pool_sweep = Time.now.to_f
31+
@smb_server_pool_mutex = Mutex.new
32+
@smb_server_request_counter = 0
2333
end
2434

2535
def setup
@@ -44,16 +54,58 @@ def on_client_close(client)
4454

4555
def smb_conn(c)
4656
@state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport}
57+
smb_pool_update(c)
4758
end
4859

4960
def smb_stop(c)
61+
# Make sure the socket is closed
62+
begin
63+
c.close
64+
# Handle any number of errors that a double-close or failed shutdown can trigger
65+
rescue ::IOError, ::EOFError,
66+
::Errno::ECONNRESET, ::Errno::ENOTCONN, ::Errno::ECONNABORTED,
67+
::Errno::ETIMEDOUT, ::Errno::ENETRESET, ::Errno::ESHUTDOWN
68+
end
69+
70+
# Delete the state table entry
5071
@state.delete(c)
5172
end
5273

5374
def smb_recv(c)
5475
smb = @state[c]
5576
smb[:data] ||= ''
56-
smb[:data] << c.get_once
77+
78+
buff = ''
79+
begin
80+
buff = c.get_once(-1, 0.25)
81+
# Handle any number of errors that a read can trigger depending on socket state
82+
rescue ::IOError, ::EOFError,
83+
::Errno::ECONNRESET, ::Errno::ENOTCONN, ::Errno::ECONNABORTED,
84+
::Errno::ETIMEDOUT, ::Errno::ENETRESET, ::Errno::ESHUTDOWN
85+
vprint_status("Dropping connection from #{smb[:name]} due to exception: #{$!.class} #{$!}")
86+
smb_stop(c)
87+
return
88+
end
89+
90+
# The client said it had data, but lied, kill the session
91+
unless buff and buff.length > 0
92+
vprint_status("Dropping connection from #{smb[:name]} due to empty payload...")
93+
smb_stop(c)
94+
return
95+
end
96+
97+
# Append the new data to the buffer
98+
smb[:data] << buff
99+
100+
# Prevent a simplistic DoS if the buffer is too big
101+
if smb[:data].length > (1024*1024*datastore['SMBServerMaximumBuffer'])
102+
vprint_status("Dropping connection from #{smb[:name]} due to oversized buffer of #{smb[:data].length} bytes...")
103+
smb_stop(c)
104+
return
105+
end
106+
107+
# Update the last-seen timestamp and purge old entries
108+
smb_pool_update(c)
57109

58110
while(smb[:data].length > 0)
59111

@@ -95,10 +147,11 @@ def smb_recv(c)
95147
pkt = CONST::SMB_BASE_PKT.make_struct
96148
pkt.from_s(buff)
97149

98-
# Only response to requests, ignore server replies
150+
# Only respond to requests, ignore server replies
99151
if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0)
100-
print_status("Ignoring server response from #{smb[:name]}")
101-
next
152+
vprint_status("Dropping connection from #{smb[:name]} due to missing client request flag")
153+
smb_stop(c)
154+
return
102155
end
103156

104157
cmd = pkt['Payload']['SMB'].v['Command']
@@ -149,6 +202,38 @@ def smb_error(cmd, c, errorclass, esn = false)
149202
pkt['Payload']['SMB'].v['ErrorClass'] = errorclass
150203
c.put(pkt.to_s)
151204
end
205+
206+
# Update the last-seen timestamp and purge old entries
207+
def smb_pool_update(c)
208+
209+
@state[c][:last_action] = Time.now.to_f
210+
@smb_server_request_counter += 1
211+
212+
unless @smb_server_request_counter % 100 == 0 ||
213+
@smb_server_last_pool_sweep + datastore['SMBServerIdleTimeout'].to_f < Time.now.to_f
214+
return
215+
end
216+
217+
# Synchronize pool sweeps in case we move to threaded services
218+
@smb_server_pool_mutex.synchronize do
219+
purge_list = []
220+
221+
@smb_server_last_pool_sweep = Time.now.to_f
222+
223+
@state.keys.each do |sc|
224+
if @state[sc][:last_action] + datastore['SMBServerIdleTimeout'].to_f < Time.now.to_f
225+
purge_list << sc
226+
end
227+
end
228+
229+
# Purge any idle connections to rescue file descriptors
230+
purge_list.each do |sc|
231+
vprint_status("Dropping connection from #{@state[sc][:name]} due to idle timeout...")
232+
smb_stop(sc)
233+
end
234+
end
235+
end
236+
152237
end
153238
end
154239

0 commit comments

Comments
 (0)