Skip to content

Commit 85f48a3

Browse files
author
Tod Beardsley
committed
Land rapid7#3738, SMBServer file descriptor updates
2 parents dbaf9c5 + 8fa666b commit 85f48a3

File tree

1 file changed

+89
-4
lines changed

1 file changed

+89
-4
lines changed

lib/msf/core/exploit/smb.rb

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,16 @@ def initialize(info = {})
661661
[
662662
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ])
663663
], self.class)
664+
665+
register_advanced_options(
666+
[
667+
OptInt.new('SMBServerMaximumBuffer', [ true, "The maximum number of data in megabytes to buffer", 2 ]),
668+
OptInt.new('SMBServerIdleTimeout', [ true, "The maximum amount of time to keep an idle session open in seconds", 120 ]),
669+
], self.class)
670+
671+
@smb_server_last_pool_sweep = Time.now.to_f
672+
@smb_server_pool_mutex = Mutex.new
673+
@smb_server_request_counter = 0
664674
end
665675

666676
def setup
@@ -685,16 +695,59 @@ def on_client_close(client)
685695

686696
def smb_conn(c)
687697
@state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport}
698+
smb_pool_update(c)
688699
end
689700

690701
def smb_stop(c)
702+
703+
# Make sure the socket is closed
704+
begin
705+
c.close
706+
# Handle any number of errors that a double-close or failed shutdown can trigger
707+
rescue ::IOError, ::EOFError,
708+
::Errno::ECONNRESET, ::Errno::ENOTCONN, ::Errno::ECONNABORTED,
709+
::Errno::ETIMEDOUT, ::Errno::ENETRESET, ::Errno::ESHUTDOWN
710+
end
711+
712+
# Delete the state table entry
691713
@state.delete(c)
692714
end
693715

694716
def smb_recv(c)
695717
smb = @state[c]
696718
smb[:data] ||= ''
697-
smb[:data] << c.get_once
719+
720+
buff = ''
721+
begin
722+
buff = c.get_once(-1, 0.25)
723+
# Handle any number of errors that a read can trigger depending on socket state
724+
rescue ::IOError, ::EOFError,
725+
::Errno::ECONNRESET, ::Errno::ENOTCONN, ::Errno::ECONNABORTED,
726+
::Errno::ETIMEDOUT, ::Errno::ENETRESET, ::Errno::ESHUTDOWN
727+
vprint_status("Dropping connection from #{smb[:name]} due to exception: #{$!.class} #{$!}")
728+
smb_stop(c)
729+
return
730+
end
731+
732+
# The client said it had data, but lied, kill the session
733+
unless buff and buff.length > 0
734+
vprint_status("Dropping connection from #{smb[:name]} due to empty payload...")
735+
smb_stop(c)
736+
return
737+
end
738+
739+
# Append the new data to the buffer
740+
smb[:data] << buff
741+
742+
# Prevent a simplistic DoS if the buffer is too big
743+
if smb[:data].length > (1024*1024*datastore['SMBServerMaximumBuffer'])
744+
vprint_status("Dropping connection from #{smb[:name]} due to oversized buffer of #{smb[:data].length} bytes...")
745+
smb_stop(c)
746+
return
747+
end
748+
749+
# Update the last-seen timestamp and purge old entries
750+
smb_pool_update(c)
698751

699752
while(smb[:data].length > 0)
700753

@@ -736,10 +789,11 @@ def smb_recv(c)
736789
pkt = CONST::SMB_BASE_PKT.make_struct
737790
pkt.from_s(buff)
738791

739-
# Only response to requests, ignore server replies
792+
# Only respond to requests, ignore server replies
740793
if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0)
741-
print_status("Ignoring server response from #{smb[:name]}")
742-
next
794+
vprint_status("Dropping connection from #{smb[:name]} due to missing client request flag")
795+
smb_stop(c)
796+
return
743797
end
744798

745799
cmd = pkt['Payload']['SMB'].v['Command']
@@ -784,6 +838,37 @@ def smb_error(cmd, c, errorclass, esn = false)
784838
c.put(pkt.to_s)
785839
end
786840

841+
# Update the last-seen timestamp and purge old entries
842+
def smb_pool_update(c)
843+
844+
@state[c][:last_action] = Time.now.to_f
845+
@smb_server_request_counter += 1
846+
847+
unless @smb_server_request_counter % 100 == 0 ||
848+
@smb_server_last_pool_sweep + datastore['SMBServerIdleTimeout'].to_f < Time.now.to_f
849+
return
850+
end
851+
852+
# Synchronize pool sweeps in case we move to threaded services
853+
@smb_server_pool_mutex.synchronize do
854+
purge_list = []
855+
856+
@smb_server_last_pool_sweep = Time.now.to_f
857+
858+
@state.keys.each do |sc|
859+
if @state[sc][:last_action] + datastore['SMBServerIdleTimeout'].to_f < Time.now.to_f
860+
purge_list << sc
861+
end
862+
end
863+
864+
# Purge any idle connections to rescue file descriptors
865+
purge_list.each do |sc|
866+
vprint_status("Dropping connection from #{@state[sc][:name]} due to idle timeout...")
867+
smb_stop(sc)
868+
end
869+
end
870+
end
871+
787872
end
788873

789874

0 commit comments

Comments
 (0)