From a2e2e37a69c27dc0cb5fbc5a76dd70ca943b4257 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 17 Jul 2014 17:17:31 -0700 Subject: [PATCH 01/17] Fix SIP options scanning --- lib/rex/proto/sip.rb | 4 + lib/rex/proto/sip/util.rb | 71 +++++++++ modules/auxiliary/scanner/sip/options.rb | 158 ++++++------------- modules/auxiliary/scanner/sip/options_tcp.rb | 95 ++--------- 4 files changed, 134 insertions(+), 194 deletions(-) create mode 100644 lib/rex/proto/sip.rb create mode 100644 lib/rex/proto/sip/util.rb diff --git a/lib/rex/proto/sip.rb b/lib/rex/proto/sip.rb new file mode 100644 index 0000000000..9fd3fa65ec --- /dev/null +++ b/lib/rex/proto/sip.rb @@ -0,0 +1,4 @@ +# encoding: UTF-8 + +# SIP protocol support +require 'rex/proto/sip/util' diff --git a/lib/rex/proto/sip/util.rb b/lib/rex/proto/sip/util.rb new file mode 100644 index 0000000000..8cb0e594bf --- /dev/null +++ b/lib/rex/proto/sip/util.rb @@ -0,0 +1,71 @@ +# encoding: UTF-8 + +module Rex + module Proto + # SIP protocol support + module SIP + # Returns a list of the values for all instances of header_name from the + # response, or nil if that header was not found + def extract_header_values(resp, header_name) + values = resp.scan(/^#{header_name}:\s*(.*)$/i) + return nil if values.empty? + values.flatten.map { |v| v.strip }.sort + end + + # Parses +resp+, extracts useful metdata and then reports on it + def parse_reply(resp) + rcode = resp.split(/\s+/)[0] + # Extract the interesting headers + metadata = { + 'agent' => extract_header_values(resp, 'User-Agent'), + 'verbs' => extract_header_values(resp, 'Allow'), + 'server' => extract_header_values(resp, 'Server'), + 'proxy' => extract_header_values(resp, 'Proxy-Require') + } + # Drop any that we don't retrieve + metadata.delete_if { |_, v| v.nil? } + + print_status("#{rhost} #{rcode} #{metadata}") + + report_service( + host: rhost, + port: rport, + proto: 'tcp', + name: 'sip' + ) + + report_note( + host: rhost, + type: 'sip_useragent', + data: metadata['agent'] + ) if metadata['agent'] + + report_note( + host: rhost, + type: 'sip_server', + data: metadata['server'] + ) if metadata['server'] + end + + def create_probe(ip, proto) + suser = Rex::Text.rand_text_alphanumeric(rand(8) + 1) + shost = Rex::Socket.source_address(ip) + src = "#{shost}:#{datastore['RPORT']}" + + data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" + data << "Via: SIP/2.0/#{proto} #{src};branch=z9hG4bK.#{format('%.8x', rand(0x100000000))};rport;alias\r\n" + data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" + data << "To: sip:#{datastore['TO']}@#{ip}\r\n" + data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" + data << "CSeq: 1 OPTIONS\r\n" + data << "Contact: sip:#{suser}@#{src}\r\n" + data << "Max-Forwards: 20\r\n" + data << "User-Agent: #{suser}\r\n" + data << "Accept: text/plain\r\n" + data << "Content-Length: 0\r\n" + data << "\r\n" + data + end + end + end +end diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index b8cd0d8a44..15f078b16d 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -1,16 +1,16 @@ +# encoding: UTF-8 ## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## - require 'msf/core' - +require 'rex/proto/sip' class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner + include Rex::Proto::SIP def initialize super( @@ -23,14 +23,13 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), - OptString.new('TO', [ false, "The destination username to probe at each host", "nobody"]), + OptString.new('TO', [false, 'The destination username to probe at each host', 'nobody']), Opt::RPORT(5060), Opt::CHOST, Opt::CPORT(5060) ], self.class) end - # Define our batch size def run_batch_size datastore['BATCHSIZE'].to_i @@ -38,123 +37,60 @@ class Metasploit3 < Msf::Auxiliary # Operate on an entire batch of hosts at once def run_batch(batch) + udp_sock = nil + idx = 0 + # Create an unbound UDP socket if no CHOST is specified, otherwise + # create a UDP socket bound to CHOST (in order to avail of pivoting) + udp_sock = Rex::Socket::Udp.create( + + 'LocalHost' => datastore['CHOST'] || nil, + 'LocalPort' => datastore['CPORT'].to_i, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + + ) + add_socket(udp_sock) + batch.each do |ip| + data = create_probe(ip, 'UDP') + begin + udp_sock.sendto(data, ip, datastore['RPORT'].to_i, 0) + rescue ::Interrupt + raise $ERROR_INFO + rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused + nil + end + handle_replies(udp_sock) if idx % 10 == 0 + idx += 1 + end begin - udp_sock = nil - idx = 0 - - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( - { - 'LocalHost' => datastore['CHOST'] || nil, - 'LocalPort' => datastore['CPORT'].to_i, - 'Context' => {'Msf' => framework, 'MsfExploit' => self} - } - ) - add_socket(udp_sock) - - batch.each do |ip| - data = create_probe(ip) - - begin - udp_sock.sendto(data, ip, datastore['RPORT'].to_i, 0) - rescue ::Interrupt - raise $! - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - - if (idx % 10 == 0) - while (r = udp_sock.recvfrom(65535, 0.01) and r[1]) - parse_reply(r) - end - end - - idx += 1 - end - - while (r = udp_sock.recvfrom(65535, 3) and r[1]) - parse_reply(r) - end - + handle_replies(udp_sock) rescue ::Interrupt - raise $! - rescue ::Exception => e + raise $ERROR_INFO + rescue => e print_error("Unknown error: #{e.class} #{e}") ensure udp_sock.close if udp_sock end end - # - # The response parsers - # - def parse_reply(pkt) - - return if not pkt[1] - - if(pkt[1] =~ /^::ffff:/) - pkt[1] = pkt[1].sub(/^::ffff:/, '') + def handle_replies(udp_sock) + r = read_reply(udp_sock) + while r && r[1] + handle_reply(r) + r = read_reply(udp_sock) end + end + + def read_reply(udp_sock) + udp_sock.recvfrom(65535, 0.01) + end + + def handle_reply(pkt) + return unless pkt[1] + + pkt[1].sub!(/^::ffff:/, '') resp = pkt[0].split(/\s+/)[1] - agent = '' - verbs = '' - serv = '' - prox = '' - - if(pkt[0] =~ /^User-Agent:\s*(.*)$/i) - agent = "agent='#{$1.strip}' " - end - - if(pkt[0] =~ /^Allow:\s+(.*)$/i) - verbs = "verbs='#{$1.strip}' " - end - - if(pkt[0] =~ /^Server:\s+(.*)$/) - serv = "server='#{$1.strip}' " - end - - if(pkt[0] =~ /^Proxy-Require:\s+(.*)$/) - serv = "proxy-required='#{$1.strip}' " - end - - print_status("#{pkt[1]} #{resp} #{agent}#{serv}#{prox}#{verbs}") - - report_service( - :host => pkt[1], - :port => pkt[2], - :proto => 'udp', - :name => 'sip' - ) - - if(not agent.empty?) - report_note( - :host => pkt[1], - :type => 'sip_useragent', - :data => agent - ) - end + parse_reply(resp) end - - def create_probe(ip) - suser = Rex::Text.rand_text_alphanumeric(rand(8)+1) - shost = Rex::Socket.source_address(ip) - src = "#{shost}:#{datastore['CPORT']}" - - data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" - data << "Via: SIP/2.0/UDP #{src};branch=z9hG4bK.#{"%.8x" % rand(0x100000000)};rport;alias\r\n" - data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" - data << "To: sip:#{datastore['TO']}@#{ip}\r\n" - data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" - data << "CSeq: 1 OPTIONS\r\n" - data << "Contact: sip:#{suser}@#{src}\r\n" - data << "Content-Length: 0\r\n" - data << "Max-Forwards: 20\r\n" - data << "User-Agent: #{suser}\r\n" - data << "Accept: text/plain\r\n" - end - - end diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index d4fb1f0b56..88d5ed813e 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -1,15 +1,17 @@ +# encoding: UTF-8 ## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' +require 'rex/proto/sip' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner + include Rex::Proto::SIP def initialize super( @@ -22,93 +24,20 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), - OptString.new('TO', [ false, "The destination username to probe at each host", "nobody"]), + OptString.new('TO', [false, 'The destination username to probe at each host', 'nobody']), Opt::RPORT(5060) ], self.class) end # Operate on a single system at a time def run_host(ip) - - begin - idx = 0 - - connect - sock.put(create_probe(ip)) - res = sock.get_once(-1, 5) - parse_reply(res) if res - - rescue ::Interrupt - raise $! - ensure - disconnect - end + connect + sock.put(create_probe(ip, 'TCP')) + res = sock.get_once(-1, 5) + parse_reply(res) if res + rescue ::Interrupt + raise $ERROR_INFO + ensure + disconnect end - - # - # The response parser - # - def parse_reply(resp) - - rcode = resp.split(/\s+/)[0] - agent = '' - verbs = '' - serv = '' - prox = '' - - if(resp =~ /^User-Agent:\s*(.*)$/i) - agent = "agent='#{$1.strip}' " - end - - if(resp =~ /^Allow:\s+(.*)$/i) - verbs = "verbs='#{$1.strip}' " - end - - if(resp =~ /^Server:\s+(.*)$/) - serv = "server='#{$1.strip}' " - end - - if(resp =~ /^Proxy-Require:\s+(.*)$/) - serv = "proxy-required='#{$1.strip}' " - end - - print_status("#{rhost} #{rcode} #{agent}#{serv}#{prox}#{verbs}") - - report_service( - :host => rhost, - :port => rport, - :proto => 'tcp', - :name => 'sip' - ) - - if(not agent.empty?) - report_note( - :host => rhost, - :type => 'sip_useragent', - :data => agent - ) - end - end - - def create_probe(ip) - suser = Rex::Text.rand_text_alphanumeric(rand(8)+1) - shost = Rex::Socket.source_address(ip) - src = "#{shost}:#{datastore['RPORT']}" - - data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" - data << "Via: SIP/2.0/TCP #{src};branch=z9hG4bK.#{"%.8x" % rand(0x100000000)};rport;alias\r\n" - data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" - data << "To: sip:#{datastore['TO']}@#{ip}\r\n" - data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" - data << "CSeq: 1 OPTIONS\r\n" - data << "Contact: sip:#{suser}@#{src}\r\n" - data << "Max-Forwards: 20\r\n" - data << "User-Agent: #{suser}\r\n" - data << "Accept: text/plain\r\n" - data << "Content-Length: 0\r\n" - data << "\r\n" - data - end - - end From d4ea3e9f2982049419009aedc59ee1aae271da9a Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 17 Jul 2014 17:27:28 -0700 Subject: [PATCH 02/17] Pass protocol down to parse_reply for report_* purposes --- lib/rex/proto/sip/util.rb | 4 ++-- modules/auxiliary/scanner/sip/options.rb | 2 +- modules/auxiliary/scanner/sip/options_tcp.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rex/proto/sip/util.rb b/lib/rex/proto/sip/util.rb index 8cb0e594bf..a7d90a8f0f 100644 --- a/lib/rex/proto/sip/util.rb +++ b/lib/rex/proto/sip/util.rb @@ -13,7 +13,7 @@ module Rex end # Parses +resp+, extracts useful metdata and then reports on it - def parse_reply(resp) + def parse_reply(resp, proto) rcode = resp.split(/\s+/)[0] # Extract the interesting headers metadata = { @@ -30,7 +30,7 @@ module Rex report_service( host: rhost, port: rport, - proto: 'tcp', + proto: proto, name: 'sip' ) diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index 15f078b16d..d1eb21be51 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -91,6 +91,6 @@ class Metasploit3 < Msf::Auxiliary pkt[1].sub!(/^::ffff:/, '') resp = pkt[0].split(/\s+/)[1] - parse_reply(resp) + parse_reply(resp, 'udp') end end diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index 88d5ed813e..550fb576f8 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -34,7 +34,7 @@ class Metasploit3 < Msf::Auxiliary connect sock.put(create_probe(ip, 'TCP')) res = sock.get_once(-1, 5) - parse_reply(res) if res + parse_reply(res, 'tcp') if res rescue ::Interrupt raise $ERROR_INFO ensure From 69aa56d8d347d7c7ece917ccb2d480e76a352f6b Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 10:26:55 -0700 Subject: [PATCH 03/17] Disable rubocop RedundantBegin for modules --- .rubocop.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3ae05511be..6ef2a9c590 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -53,4 +53,22 @@ Style/StringLiterals: Style/WordArray: Enabled: false - Description: 'Metasploit prefers consistent use of []' \ No newline at end of file + Description: 'Metasploit prefers consistent use of []' + +Style/RedundantBegin: + Exclude: + # this pattern is very common and somewhat unavoidable + # def run_host(ip) + # begin + # ... + # rescue ... + # ... + # ensure + # disconnect + # end + # end + - 'modules/**/*' + +Documentation: + Exclude: + - 'modules/**/*' From 02e41c27e70f70809ae6c592f72c89e7d5b347fe Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 10:29:14 -0700 Subject: [PATCH 04/17] Split SIP response parsing out on its own, add unit tests. Passes rspec but fails in framework. WIP. --- lib/rex/proto/sip.rb | 1 + lib/rex/proto/sip/response.rb | 35 +++++++++ lib/rex/proto/sip/util.rb | 79 ++++++++++++-------- modules/auxiliary/scanner/sip/options.rb | 4 +- modules/auxiliary/scanner/sip/options_tcp.rb | 18 +++-- spec/lib/rex/proto/sip/response_spec.rb | 37 +++++++++ spec/lib/rex/proto/sip/util_spec.rb | 19 +++++ 7 files changed, 152 insertions(+), 41 deletions(-) create mode 100644 lib/rex/proto/sip/response.rb create mode 100644 spec/lib/rex/proto/sip/response_spec.rb create mode 100644 spec/lib/rex/proto/sip/util_spec.rb diff --git a/lib/rex/proto/sip.rb b/lib/rex/proto/sip.rb index 9fd3fa65ec..165565872d 100644 --- a/lib/rex/proto/sip.rb +++ b/lib/rex/proto/sip.rb @@ -1,4 +1,5 @@ # encoding: UTF-8 # SIP protocol support +require 'rex/proto/sip/response' require 'rex/proto/sip/util' diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb new file mode 100644 index 0000000000..a817115b43 --- /dev/null +++ b/lib/rex/proto/sip/response.rb @@ -0,0 +1,35 @@ +# encoding: UTF-8 + +require 'rex/proto/sip/util' + +module Rex + module Proto + # SIP protocol support + module SIP + SIP_STATUS_REGEX = /^SIP\/(\d\.\d) (\d{3})\s*(.*)$/ + + # Represents a SIP response message + class Response + attr_accessor :version, :code, :message, :headers + + def header(name) + @headers.select { |k, _| k.downcase == name.downcase }.last + end + + def self.parse(data) + response = Response.new + # do some basic sanity checking on this response to ensure that it is SIP + status_line = data.split(/\r\n/)[0] + unless status_line && status_line =~ SIP_STATUS_REGEX + fail(ArgumentError, 'Does not start with a valid SIP status line') + end + response.version = Regexp.last_match(1) + response.code = Regexp.last_match(2) + response.message = Regexp.last_match(3) + response.headers = ::Rex::Proto::SIP.extract_headers(data) + response + end + end + end + end +end diff --git a/lib/rex/proto/sip/util.rb b/lib/rex/proto/sip/util.rb index a7d90a8f0f..969a668e83 100644 --- a/lib/rex/proto/sip/util.rb +++ b/lib/rex/proto/sip/util.rb @@ -1,32 +1,35 @@ # encoding: UTF-8 +require 'rex/proto/sip/response' + module Rex module Proto # SIP protocol support module SIP - # Returns a list of the values for all instances of header_name from the - # response, or nil if that header was not found - def extract_header_values(resp, header_name) - values = resp.scan(/^#{header_name}:\s*(.*)$/i) - return nil if values.empty? - values.flatten.map { |v| v.strip }.sort + # Returns a hash of header name to values mapping + # from the provided message, or nil if no headers + # are found + def extract_headers(message) + pairs = message.scan(/^([^\s:]+):\s*(.*)$/) + return nil if pairs.empty? + headers = {} + pairs.each do |pair| + headers[pair.first] ||= [] + headers[pair.first] << pair.last.strip + end + headers end - # Parses +resp+, extracts useful metdata and then reports on it - def parse_reply(resp, proto) - rcode = resp.split(/\s+/)[0] - # Extract the interesting headers - metadata = { - 'agent' => extract_header_values(resp, 'User-Agent'), - 'verbs' => extract_header_values(resp, 'Allow'), - 'server' => extract_header_values(resp, 'Server'), - 'proxy' => extract_header_values(resp, 'Proxy-Require') - } - # Drop any that we don't retrieve - metadata.delete_if { |_, v| v.nil? } - - print_status("#{rhost} #{rcode} #{metadata}") + # Parses +response+, extracts useful metdata and then reports on it + def parse_response(response, proto, desired_headers = %w(User-Agent Server)) + endpoint = "#{rhost}:#{rport}/#{proto}" + begin + options_response = Rex::Proto::SIP::Response.parse(response) + rescue ArgumentError => e + vprint_error("#{endpoint} is not SIP: #{e}") + end + # We know it is SIP, so report report_service( host: rhost, port: rport, @@ -34,17 +37,33 @@ module Rex name: 'sip' ) - report_note( - host: rhost, - type: 'sip_useragent', - data: metadata['agent'] - ) if metadata['agent'] + # Do header extraction as necessary + extracted_headers = {} + unless desired_headers.nil? || desired_headers.empty? + options_response.headers.select { |k, _| desired_headers.any? { |h| h.downcase == k.downcase } }.each do |header| + name = header.first.downcase + values = header.last + extracted_headers[name] ||= [] + extracted_headers[name] << values + end - report_note( - host: rhost, - type: 'sip_server', - data: metadata['server'] - ) if metadata['server'] + # report on any extracted headers + extracted_headers.each do |k, v| + report_note( + host: rhost, + port: rport, + proto: proto, + type: "sip_#{k}", + data: v + ) + end + end + + if extracted_headers.empty? + print_status("#{endpoint} #{version} #{status}") + else + print_status("#{endpoint} #{version} #{status}: #{extracted_headers}") + end end def create_probe(ip, proto) diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index d1eb21be51..5d899f7711 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -42,11 +42,9 @@ class Metasploit3 < Msf::Auxiliary # Create an unbound UDP socket if no CHOST is specified, otherwise # create a UDP socket bound to CHOST (in order to avail of pivoting) udp_sock = Rex::Socket::Udp.create( - 'LocalHost' => datastore['CHOST'] || nil, 'LocalPort' => datastore['CPORT'].to_i, 'Context' => { 'Msf' => framework, 'MsfExploit' => self } - ) add_socket(udp_sock) batch.each do |ip| @@ -91,6 +89,6 @@ class Metasploit3 < Msf::Auxiliary pkt[1].sub!(/^::ffff:/, '') resp = pkt[0].split(/\s+/)[1] - parse_reply(resp, 'udp') + parse_response(resp, 'udp') end end diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index 550fb576f8..11da563dcd 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -31,13 +31,15 @@ class Metasploit3 < Msf::Auxiliary # Operate on a single system at a time def run_host(ip) - connect - sock.put(create_probe(ip, 'TCP')) - res = sock.get_once(-1, 5) - parse_reply(res, 'tcp') if res - rescue ::Interrupt - raise $ERROR_INFO - ensure - disconnect + begin + connect + sock.put(create_probe(ip, 'TCP')) + res = sock.get_once(-1, 5) + parse_response(res, 'tcp') if res + rescue ::Interrupt + raise $ERROR_INFO + ensure + disconnect + end end end diff --git a/spec/lib/rex/proto/sip/response_spec.rb b/spec/lib/rex/proto/sip/response_spec.rb new file mode 100644 index 0000000000..4a3fbc1949 --- /dev/null +++ b/spec/lib/rex/proto/sip/response_spec.rb @@ -0,0 +1,37 @@ +# encoding: UTF-8 + +require 'rex/proto/sip/response' +include Rex::Proto::SIP + +describe 'Rex::Proto::SIP::Response parsing' do + describe 'Parses vaild responses correctly' do + specify do + r = Response.parse('SIP/1.0 123 Sure, OK') + r.version.should eq('1.0') + r.code.should eq('123') + r.message.should eq('Sure, OK') + r.headers.should be_nil + end + + specify do + r = Response.parse("SIP/2.0 200 OK\r\nFoo: bar\r\nBlah: 0\r\n") + r.version.should eq('2.0') + r.code.should eq('200') + r.message.should eq('OK') + r.headers.should eq('Foo' => %w(bar), 'Blah' => %w(0)) + end + end + + describe 'Parses invalid responses correctly' do + [ + '', + 'aldkjfakdjfasdf', + 'SIP/foo 200 OK', + 'SIP/2.0 foo OK' + ].each do |r| + it 'Should fail to parse an invalid response' do + expect { Response.parse(r) }.to raise_error(ArgumentError, /status/) + end + end + end +end diff --git a/spec/lib/rex/proto/sip/util_spec.rb b/spec/lib/rex/proto/sip/util_spec.rb new file mode 100644 index 0000000000..5e655995dd --- /dev/null +++ b/spec/lib/rex/proto/sip/util_spec.rb @@ -0,0 +1,19 @@ +# encoding: UTF-8 + +require 'rex/proto/sip/util' +include Rex::Proto::SIP + +describe 'Rex::Proto::SIP SIP utility methods' do + describe 'Extracts headers correctly' do + headerless_response = 'Look, no headers' + specify { extract_headers(headerless_response).should be_nil } + response_with_headers = < %w(v1), 'H2' => %w(v2 v21), 'H3' => %w(v3) } + specify { extract_headers(response_with_headers).should == expected_headers } + end +end From e3753e36491381df89901c4019ad375d242ef85f Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 11:25:20 -0700 Subject: [PATCH 05/17] Refactor SIP response parsing for future improvements --- lib/rex/proto/sip/response.rb | 42 ++++++++++++++++++++----- lib/rex/proto/sip/util.rb | 32 +++++-------------- spec/lib/rex/proto/sip/response_spec.rb | 14 ++++++--- spec/lib/rex/proto/sip/util_spec.rb | 19 ----------- 4 files changed, 51 insertions(+), 56 deletions(-) delete mode 100644 spec/lib/rex/proto/sip/util_spec.rb diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb index a817115b43..f292fb1623 100644 --- a/lib/rex/proto/sip/response.rb +++ b/lib/rex/proto/sip/response.rb @@ -8,25 +8,51 @@ module Rex module SIP SIP_STATUS_REGEX = /^SIP\/(\d\.\d) (\d{3})\s*(.*)$/ - # Represents a SIP response message - class Response - attr_accessor :version, :code, :message, :headers + class Message + attr_accessor :headers - def header(name) - @headers.select { |k, _| k.downcase == name.downcase }.last + def initialize + @headers = {} end + # Returns a list of all values from all +name+ headers, regardless of case, + # or nil if no matching header is found + def header(name) + matches = @headers.select { |k, _| k.downcase == name.downcase } + return nil if matches.empty? + matches.values.flatten + end + + # Returns a hash of header name to values mapping + # from the provided message, or nil if no headers + # are found + def self.extract_headers(message) + pairs = message.scan(/^([^\s:]+):\s*(.*)$/) + return nil if pairs.empty? + headers = {} + pairs.each do |pair| + headers[pair.first] ||= [] + headers[pair.first] << pair.last.strip + end + headers + end + end + + # Represents a SIP response message + class Response < Message + attr_accessor :code, :message, :status_line, :version + def self.parse(data) response = Response.new # do some basic sanity checking on this response to ensure that it is SIP - status_line = data.split(/\r\n/)[0] - unless status_line && status_line =~ SIP_STATUS_REGEX + response.status_line = data.split(/\r\n/)[0] + unless response.status_line && response.status_line =~ SIP_STATUS_REGEX fail(ArgumentError, 'Does not start with a valid SIP status line') end response.version = Regexp.last_match(1) response.code = Regexp.last_match(2) response.message = Regexp.last_match(3) - response.headers = ::Rex::Proto::SIP.extract_headers(data) + response.headers = extract_headers(data) response end end diff --git a/lib/rex/proto/sip/util.rb b/lib/rex/proto/sip/util.rb index 969a668e83..fcd9b8f0b1 100644 --- a/lib/rex/proto/sip/util.rb +++ b/lib/rex/proto/sip/util.rb @@ -6,20 +6,6 @@ module Rex module Proto # SIP protocol support module SIP - # Returns a hash of header name to values mapping - # from the provided message, or nil if no headers - # are found - def extract_headers(message) - pairs = message.scan(/^([^\s:]+):\s*(.*)$/) - return nil if pairs.empty? - headers = {} - pairs.each do |pair| - headers[pair.first] ||= [] - headers[pair.first] << pair.last.strip - end - headers - end - # Parses +response+, extracts useful metdata and then reports on it def parse_response(response, proto, desired_headers = %w(User-Agent Server)) endpoint = "#{rhost}:#{rport}/#{proto}" @@ -27,6 +13,7 @@ module Rex options_response = Rex::Proto::SIP::Response.parse(response) rescue ArgumentError => e vprint_error("#{endpoint} is not SIP: #{e}") + return end # We know it is SIP, so report @@ -40,11 +27,10 @@ module Rex # Do header extraction as necessary extracted_headers = {} unless desired_headers.nil? || desired_headers.empty? - options_response.headers.select { |k, _| desired_headers.any? { |h| h.downcase == k.downcase } }.each do |header| - name = header.first.downcase - values = header.last - extracted_headers[name] ||= [] - extracted_headers[name] << values + desired_headers.each do |desired_header| + next unless found_header = options_response.header(desired_header) + extracted_headers[desired_header] ||= [] + extracted_headers[desired_header] |= found_header end # report on any extracted headers @@ -59,11 +45,9 @@ module Rex end end - if extracted_headers.empty? - print_status("#{endpoint} #{version} #{status}") - else - print_status("#{endpoint} #{version} #{status}: #{extracted_headers}") - end + status = "#{endpoint} #{options_response.status_line}" + status += ": #{extracted_headers}" unless extracted_headers.empty? + print_status(status) end def create_probe(ip, proto) diff --git a/spec/lib/rex/proto/sip/response_spec.rb b/spec/lib/rex/proto/sip/response_spec.rb index 4a3fbc1949..6c3561d7cd 100644 --- a/spec/lib/rex/proto/sip/response_spec.rb +++ b/spec/lib/rex/proto/sip/response_spec.rb @@ -1,12 +1,13 @@ # encoding: UTF-8 require 'rex/proto/sip/response' -include Rex::Proto::SIP describe 'Rex::Proto::SIP::Response parsing' do describe 'Parses vaild responses correctly' do specify do - r = Response.parse('SIP/1.0 123 Sure, OK') + resp = 'SIP/1.0 123 Sure, OK' + r = ::Rex::Proto::SIP::Response.parse(resp) + r.status_line.should eq(resp) r.version.should eq('1.0') r.code.should eq('123') r.message.should eq('Sure, OK') @@ -14,11 +15,14 @@ describe 'Rex::Proto::SIP::Response parsing' do end specify do - r = Response.parse("SIP/2.0 200 OK\r\nFoo: bar\r\nBlah: 0\r\n") + resp = "SIP/2.0 200 OK\r\nFoo: bar\r\nBlah: 0\r\nFoO: blaf\r\n" + r = ::Rex::Proto::SIP::Response.parse(resp) + r.status_line.should eq('SIP/2.0 200 OK') r.version.should eq('2.0') r.code.should eq('200') r.message.should eq('OK') - r.headers.should eq('Foo' => %w(bar), 'Blah' => %w(0)) + r.headers.should eq('Foo' => %w(bar), 'Blah' => %w(0), 'FoO' => %w(blaf)) + r.header('Foo').should eq %w(bar blaf) end end @@ -30,7 +34,7 @@ describe 'Rex::Proto::SIP::Response parsing' do 'SIP/2.0 foo OK' ].each do |r| it 'Should fail to parse an invalid response' do - expect { Response.parse(r) }.to raise_error(ArgumentError, /status/) + expect { ::Rex::Proto::SIP::Response.parse(r) }.to raise_error(ArgumentError, /status/) end end end diff --git a/spec/lib/rex/proto/sip/util_spec.rb b/spec/lib/rex/proto/sip/util_spec.rb deleted file mode 100644 index 5e655995dd..0000000000 --- a/spec/lib/rex/proto/sip/util_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# encoding: UTF-8 - -require 'rex/proto/sip/util' -include Rex::Proto::SIP - -describe 'Rex::Proto::SIP SIP utility methods' do - describe 'Extracts headers correctly' do - headerless_response = 'Look, no headers' - specify { extract_headers(headerless_response).should be_nil } - response_with_headers = < %w(v1), 'H2' => %w(v2 v21), 'H3' => %w(v3) } - specify { extract_headers(response_with_headers).should == expected_headers } - end -end From fc67aed174d663fdfcebbbf47817d04b09e91616 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 11:33:47 -0700 Subject: [PATCH 06/17] Correct style and doc issues, tidy failure message when not SIP --- lib/rex/proto/sip/response.rb | 4 +++- lib/rex/proto/sip/util.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb index f292fb1623..2e15b14788 100644 --- a/lib/rex/proto/sip/response.rb +++ b/lib/rex/proto/sip/response.rb @@ -8,6 +8,7 @@ module Rex module SIP SIP_STATUS_REGEX = /^SIP\/(\d\.\d) (\d{3})\s*(.*)$/ + # Represents a generic SIP message class Message attr_accessor :headers @@ -42,12 +43,13 @@ module Rex class Response < Message attr_accessor :code, :message, :status_line, :version + # Parses +data+, constructs and returns a Response def self.parse(data) response = Response.new # do some basic sanity checking on this response to ensure that it is SIP response.status_line = data.split(/\r\n/)[0] unless response.status_line && response.status_line =~ SIP_STATUS_REGEX - fail(ArgumentError, 'Does not start with a valid SIP status line') + fail(ArgumentError, 'Response data does not start with a valid SIP status line') end response.version = Regexp.last_match(1) response.code = Regexp.last_match(2) diff --git a/lib/rex/proto/sip/util.rb b/lib/rex/proto/sip/util.rb index fcd9b8f0b1..00271270af 100644 --- a/lib/rex/proto/sip/util.rb +++ b/lib/rex/proto/sip/util.rb @@ -28,7 +28,7 @@ module Rex extracted_headers = {} unless desired_headers.nil? || desired_headers.empty? desired_headers.each do |desired_header| - next unless found_header = options_response.header(desired_header) + next unless (found_header = options_response.header(desired_header)) extracted_headers[desired_header] ||= [] extracted_headers[desired_header] |= found_header end From c2e70446ed290238d49b9d580f905f5816b1d0f6 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 11:52:18 -0700 Subject: [PATCH 07/17] Move SIP module stuff to Msf::Exploit::Remote::SIP --- lib/msf/core/exploit/mixins.rb | 1 + lib/msf/core/exploit/sip.rb | 72 +++++++++++++++++++ lib/rex/proto/sip.rb | 1 - lib/rex/proto/sip/response.rb | 2 - lib/rex/proto/sip/util.rb | 74 -------------------- modules/auxiliary/scanner/sip/options.rb | 3 +- modules/auxiliary/scanner/sip/options_tcp.rb | 3 +- 7 files changed, 75 insertions(+), 81 deletions(-) create mode 100644 lib/msf/core/exploit/sip.rb delete mode 100644 lib/rex/proto/sip/util.rb diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 9f2fdd9669..c5e2b0fc1b 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -57,6 +57,7 @@ require 'msf/core/exploit/wdbrpc' require 'msf/core/exploit/wdbrpc_client' require 'msf/core/exploit/afp' require 'msf/core/exploit/realport' +require 'msf/core/exploit/sip' # Telephony require 'msf/core/exploit/dialup' diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb new file mode 100644 index 0000000000..4cfe9088f2 --- /dev/null +++ b/lib/msf/core/exploit/sip.rb @@ -0,0 +1,72 @@ +# encoding: UTF-8 + +require 'rex/proto/sip/response' + +module Msf + # SIP protocol support + module Exploit::Remote::SIP + # Parses +response+, extracts useful metdata and then reports on it + def parse_response(response, proto, desired_headers = %w(User-Agent Server)) + endpoint = "#{rhost}:#{rport}/#{proto}" + begin + options_response = Rex::Proto::SIP::Response.parse(response) + rescue ArgumentError => e + vprint_error("#{endpoint} is not SIP: #{e}") + return + end + + # We know it is SIP, so report + report_service( + host: rhost, + port: rport, + proto: proto, + name: 'sip' + ) + + # Do header extraction as necessary + extracted_headers = {} + unless desired_headers.nil? || desired_headers.empty? + desired_headers.each do |desired_header| + next unless (found_header = options_response.header(desired_header)) + extracted_headers[desired_header] ||= [] + extracted_headers[desired_header] |= found_header + end + + # report on any extracted headers + extracted_headers.each do |k, v| + report_note( + host: rhost, + port: rport, + proto: proto, + type: "sip_#{k}", + data: v + ) + end + end + + status = "#{endpoint} #{options_response.status_line}" + status += ": #{extracted_headers}" unless extracted_headers.empty? + print_status(status) + end + + def create_probe(ip, proto) + suser = Rex::Text.rand_text_alphanumeric(rand(8) + 1) + shost = Rex::Socket.source_address(ip) + src = "#{shost}:#{datastore['RPORT']}" + + data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" + data << "Via: SIP/2.0/#{proto} #{src};branch=z9hG4bK.#{format('%.8x', rand(0x100000000))};rport;alias\r\n" + data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" + data << "To: sip:#{datastore['TO']}@#{ip}\r\n" + data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" + data << "CSeq: 1 OPTIONS\r\n" + data << "Contact: sip:#{suser}@#{src}\r\n" + data << "Max-Forwards: 20\r\n" + data << "User-Agent: #{suser}\r\n" + data << "Accept: text/plain\r\n" + data << "Content-Length: 0\r\n" + data << "\r\n" + data + end + end +end diff --git a/lib/rex/proto/sip.rb b/lib/rex/proto/sip.rb index 165565872d..1a3c67546b 100644 --- a/lib/rex/proto/sip.rb +++ b/lib/rex/proto/sip.rb @@ -2,4 +2,3 @@ # SIP protocol support require 'rex/proto/sip/response' -require 'rex/proto/sip/util' diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb index 2e15b14788..ccced9d2b0 100644 --- a/lib/rex/proto/sip/response.rb +++ b/lib/rex/proto/sip/response.rb @@ -1,7 +1,5 @@ # encoding: UTF-8 -require 'rex/proto/sip/util' - module Rex module Proto # SIP protocol support diff --git a/lib/rex/proto/sip/util.rb b/lib/rex/proto/sip/util.rb deleted file mode 100644 index 00271270af..0000000000 --- a/lib/rex/proto/sip/util.rb +++ /dev/null @@ -1,74 +0,0 @@ -# encoding: UTF-8 - -require 'rex/proto/sip/response' - -module Rex - module Proto - # SIP protocol support - module SIP - # Parses +response+, extracts useful metdata and then reports on it - def parse_response(response, proto, desired_headers = %w(User-Agent Server)) - endpoint = "#{rhost}:#{rport}/#{proto}" - begin - options_response = Rex::Proto::SIP::Response.parse(response) - rescue ArgumentError => e - vprint_error("#{endpoint} is not SIP: #{e}") - return - end - - # We know it is SIP, so report - report_service( - host: rhost, - port: rport, - proto: proto, - name: 'sip' - ) - - # Do header extraction as necessary - extracted_headers = {} - unless desired_headers.nil? || desired_headers.empty? - desired_headers.each do |desired_header| - next unless (found_header = options_response.header(desired_header)) - extracted_headers[desired_header] ||= [] - extracted_headers[desired_header] |= found_header - end - - # report on any extracted headers - extracted_headers.each do |k, v| - report_note( - host: rhost, - port: rport, - proto: proto, - type: "sip_#{k}", - data: v - ) - end - end - - status = "#{endpoint} #{options_response.status_line}" - status += ": #{extracted_headers}" unless extracted_headers.empty? - print_status(status) - end - - def create_probe(ip, proto) - suser = Rex::Text.rand_text_alphanumeric(rand(8) + 1) - shost = Rex::Socket.source_address(ip) - src = "#{shost}:#{datastore['RPORT']}" - - data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" - data << "Via: SIP/2.0/#{proto} #{src};branch=z9hG4bK.#{format('%.8x', rand(0x100000000))};rport;alias\r\n" - data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" - data << "To: sip:#{datastore['TO']}@#{ip}\r\n" - data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" - data << "CSeq: 1 OPTIONS\r\n" - data << "Contact: sip:#{suser}@#{src}\r\n" - data << "Max-Forwards: 20\r\n" - data << "User-Agent: #{suser}\r\n" - data << "Accept: text/plain\r\n" - data << "Content-Length: 0\r\n" - data << "\r\n" - data - end - end - end -end diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index 5d899f7711..6ed364e582 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -5,12 +5,11 @@ ## require 'msf/core' -require 'rex/proto/sip' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner - include Rex::Proto::SIP + include Msf::Exploit::Remote::SIP def initialize super( diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index 11da563dcd..caa1d66e02 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -5,13 +5,12 @@ ## require 'msf/core' -require 'rex/proto/sip' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner - include Rex::Proto::SIP + include Msf::Exploit::Remote::SIP def initialize super( From 50d90defbc4a5af267c9cddc65f1543be03f31ad Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 12:33:27 -0700 Subject: [PATCH 08/17] Use a correct default Accept header -- responses++ --- lib/msf/core/exploit/sip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb index 4cfe9088f2..dc19a340c4 100644 --- a/lib/msf/core/exploit/sip.rb +++ b/lib/msf/core/exploit/sip.rb @@ -63,7 +63,7 @@ module Msf data << "Contact: sip:#{suser}@#{src}\r\n" data << "Max-Forwards: 20\r\n" data << "User-Agent: #{suser}\r\n" - data << "Accept: text/plain\r\n" + data << "Accept: application/sdp\r\n" data << "Content-Length: 0\r\n" data << "\r\n" data From 637f86f37da16e0c00193e2838d82e7ed0dc2b71 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 13:27:21 -0700 Subject: [PATCH 09/17] Gut SIP UDP stuff, use Msf::Auxiliary::UDPScanner --- lib/msf/core/exploit/sip.rb | 8 ++- lib/rex/proto/sip/response.rb | 2 +- modules/auxiliary/scanner/sip/options.rb | 70 +++----------------- modules/auxiliary/scanner/sip/options_tcp.rb | 2 +- 4 files changed, 17 insertions(+), 65 deletions(-) diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb index dc19a340c4..ef0d03f9a1 100644 --- a/lib/msf/core/exploit/sip.rb +++ b/lib/msf/core/exploit/sip.rb @@ -5,14 +5,15 @@ require 'rex/proto/sip/response' module Msf # SIP protocol support module Exploit::Remote::SIP - # Parses +response+, extracts useful metdata and then reports on it - def parse_response(response, proto, desired_headers = %w(User-Agent Server)) + # Parses +response+, extracts useful metdata and then reports on it. + # Returns true iff the response was a valid SIP response + def parse_response(response, rhost, proto, desired_headers = %w(User-Agent Server)) endpoint = "#{rhost}:#{rport}/#{proto}" begin options_response = Rex::Proto::SIP::Response.parse(response) rescue ArgumentError => e vprint_error("#{endpoint} is not SIP: #{e}") - return + return false end # We know it is SIP, so report @@ -47,6 +48,7 @@ module Msf status = "#{endpoint} #{options_response.status_line}" status += ": #{extracted_headers}" unless extracted_headers.empty? print_status(status) + true end def create_probe(ip, proto) diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb index ccced9d2b0..7fd9233ef8 100644 --- a/lib/rex/proto/sip/response.rb +++ b/lib/rex/proto/sip/response.rb @@ -47,7 +47,7 @@ module Rex # do some basic sanity checking on this response to ensure that it is SIP response.status_line = data.split(/\r\n/)[0] unless response.status_line && response.status_line =~ SIP_STATUS_REGEX - fail(ArgumentError, 'Response data does not start with a valid SIP status line') + fail(ArgumentError, "Invalid SIP status line: #{response.status_line}") end response.version = Regexp.last_match(1) response.code = Regexp.last_match(2) diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index 6ed364e582..bc5702a2e8 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -7,8 +7,9 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::Udp include Msf::Auxiliary::Report - include Msf::Auxiliary::Scanner + include Msf::Auxiliary::UDPScanner include Msf::Exploit::Remote::SIP def initialize @@ -23,71 +24,20 @@ class Metasploit3 < Msf::Auxiliary [ OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), OptString.new('TO', [false, 'The destination username to probe at each host', 'nobody']), - Opt::RPORT(5060), - Opt::CHOST, - Opt::CPORT(5060) + Opt::RPORT(5060) ], self.class) end - # Define our batch size - def run_batch_size - datastore['BATCHSIZE'].to_i + def scanner_prescan(batch) + print_status("Sending SIP UDP OPTIONS requests to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + @res = {} end - # Operate on an entire batch of hosts at once - def run_batch(batch) - udp_sock = nil - idx = 0 - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( - 'LocalHost' => datastore['CHOST'] || nil, - 'LocalPort' => datastore['CPORT'].to_i, - 'Context' => { 'Msf' => framework, 'MsfExploit' => self } - ) - add_socket(udp_sock) - batch.each do |ip| - data = create_probe(ip, 'UDP') - begin - udp_sock.sendto(data, ip, datastore['RPORT'].to_i, 0) - rescue ::Interrupt - raise $ERROR_INFO - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - end - handle_replies(udp_sock) if idx % 10 == 0 - idx += 1 - end - - begin - handle_replies(udp_sock) - rescue ::Interrupt - raise $ERROR_INFO - rescue => e - print_error("Unknown error: #{e.class} #{e}") - ensure - udp_sock.close if udp_sock - end + def scan_host(ip) + scanner_send(create_probe(ip, 'UDP'), ip, datastore['RPORT']) end - def handle_replies(udp_sock) - r = read_reply(udp_sock) - while r && r[1] - handle_reply(r) - r = read_reply(udp_sock) - end - end - - def read_reply(udp_sock) - udp_sock.recvfrom(65535, 0.01) - end - - def handle_reply(pkt) - return unless pkt[1] - - pkt[1].sub!(/^::ffff:/, '') - - resp = pkt[0].split(/\s+/)[1] - parse_response(resp, 'udp') + def scanner_process(data, shost, _) + parse_response(data, shost, 'UDP') end end diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index caa1d66e02..f373cae7c3 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -34,7 +34,7 @@ class Metasploit3 < Msf::Auxiliary connect sock.put(create_probe(ip, 'TCP')) res = sock.get_once(-1, 5) - parse_response(res, 'tcp') if res + parse_response(res, rhost, 'tcp') if res rescue ::Interrupt raise $ERROR_INFO ensure From b760815c86307b2b13e10251279c53baf31e1d5e Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 14:13:20 -0700 Subject: [PATCH 10/17] Also pull the Allow headers (previous behavior) --- lib/msf/core/exploit/sip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb index ef0d03f9a1..507c05da49 100644 --- a/lib/msf/core/exploit/sip.rb +++ b/lib/msf/core/exploit/sip.rb @@ -7,7 +7,7 @@ module Msf module Exploit::Remote::SIP # Parses +response+, extracts useful metdata and then reports on it. # Returns true iff the response was a valid SIP response - def parse_response(response, rhost, proto, desired_headers = %w(User-Agent Server)) + def parse_response(response, rhost, proto, desired_headers = %w(User-Agent Server Allow)) endpoint = "#{rhost}:#{rport}/#{proto}" begin options_response = Rex::Proto::SIP::Response.parse(response) From 9955cb5b27f1da2e9b9ad6b65642962d3fc18bb2 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 17:39:52 -0700 Subject: [PATCH 11/17] Enforce proper protocol case where necessary --- lib/msf/core/exploit/sip.rb | 6 +++--- modules/auxiliary/scanner/sip/options.rb | 4 ++-- modules/auxiliary/scanner/sip/options_tcp.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb index 507c05da49..2087ddaa00 100644 --- a/lib/msf/core/exploit/sip.rb +++ b/lib/msf/core/exploit/sip.rb @@ -20,7 +20,7 @@ module Msf report_service( host: rhost, port: rport, - proto: proto, + proto: proto.downcase, name: 'sip' ) @@ -38,7 +38,7 @@ module Msf report_note( host: rhost, port: rport, - proto: proto, + proto: proto.downcase, type: "sip_#{k}", data: v ) @@ -57,7 +57,7 @@ module Msf src = "#{shost}:#{datastore['RPORT']}" data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" - data << "Via: SIP/2.0/#{proto} #{src};branch=z9hG4bK.#{format('%.8x', rand(0x100000000))};rport;alias\r\n" + data << "Via: SIP/2.0/#{proto.upcase} #{src};branch=z9hG4bK.#{format('%.8x', rand(0x100000000))};rport;alias\r\n" data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" data << "To: sip:#{datastore['TO']}@#{ip}\r\n" data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index bc5702a2e8..7acf4b25bd 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -34,10 +34,10 @@ class Metasploit3 < Msf::Auxiliary end def scan_host(ip) - scanner_send(create_probe(ip, 'UDP'), ip, datastore['RPORT']) + scanner_send(create_probe(ip, 'udp'), ip, datastore['RPORT']) end def scanner_process(data, shost, _) - parse_response(data, shost, 'UDP') + parse_response(data, shost, 'udp') end end diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index f373cae7c3..ccf8569bb2 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) begin connect - sock.put(create_probe(ip, 'TCP')) + sock.put(create_probe(ip, 'tcp')) res = sock.get_once(-1, 5) parse_response(res, rhost, 'tcp') if res rescue ::Interrupt From a4f623a955f920972c4d4f9649c0f580833a2675 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 18 Jul 2014 17:40:32 -0700 Subject: [PATCH 12/17] Show port and protocol when printing service notes, not just name --- lib/msf/ui/console/command_dispatcher/db.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 598d64ef8d..25d8590752 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -1109,8 +1109,9 @@ class Db end end if (note.service) - name = (note.service.name ? note.service.name : "#{note.service.port}/#{note.service.proto}") - msg << " service=#{name}" + msg << " service=#{note.service.name}" if note.service.name + msg << " port=#{note.service.port}" if note.service.port + msg << " protocol=#{note.service.proto}" if note.service.proto end msg << " type=#{note.ntype} data=#{note.data.inspect}" print_status(msg) From 6185721a616f73876b2232af1273b3af3fa94818 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Mon, 21 Jul 2014 13:40:18 -0700 Subject: [PATCH 13/17] Address @hmoore-r7's feedback regarding binary encoding --- lib/msf/core/exploit/sip.rb | 2 +- lib/rex/proto/sip.rb | 2 +- lib/rex/proto/sip/response.rb | 2 +- modules/auxiliary/scanner/sip/options.rb | 2 +- modules/auxiliary/scanner/sip/options_tcp.rb | 2 +- spec/lib/rex/proto/sip/response_spec.rb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb index 2087ddaa00..ffcc091fe8 100644 --- a/lib/msf/core/exploit/sip.rb +++ b/lib/msf/core/exploit/sip.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# -*- coding: binary -*- require 'rex/proto/sip/response' diff --git a/lib/rex/proto/sip.rb b/lib/rex/proto/sip.rb index 1a3c67546b..2550ee0948 100644 --- a/lib/rex/proto/sip.rb +++ b/lib/rex/proto/sip.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# -*- coding: binary -*- # SIP protocol support require 'rex/proto/sip/response' diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb index 7fd9233ef8..c770fc54b6 100644 --- a/lib/rex/proto/sip/response.rb +++ b/lib/rex/proto/sip/response.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# -*- coding: binary -*- module Rex module Proto diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index 7acf4b25bd..b44a84f30a 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# -*- coding: binary -*- ## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index ccf8569bb2..5f064f55b2 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# -*- coding: binary -*- ## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework diff --git a/spec/lib/rex/proto/sip/response_spec.rb b/spec/lib/rex/proto/sip/response_spec.rb index 6c3561d7cd..2f9715bd93 100644 --- a/spec/lib/rex/proto/sip/response_spec.rb +++ b/spec/lib/rex/proto/sip/response_spec.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# -*- coding: binary -*- require 'rex/proto/sip/response' From bfa89bb3a55835d5fbbf60ffc11d2b853362ff66 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Mon, 21 Jul 2014 14:22:15 -0700 Subject: [PATCH 14/17] Enforce binary encoding on non-modules, no encoding on modules --- .rubocop.yml | 5 +++++ lib/rex/proto/sip.rb | 2 +- lib/rex/proto/sip/response.rb | 2 +- modules/auxiliary/scanner/sip/options.rb | 1 - modules/auxiliary/scanner/sip/options_tcp.rb | 1 - tools/msftidy.rb | 23 ++++++++++++++++++++ 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6ef2a9c590..c9ba4d1bb3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -39,6 +39,11 @@ Style/MethodLength: often exceed 200 lines. Max: 300 +# Basically everything in metasploit needs binary encoding, not UTF-8. +# Disable this here and enforce it through msftidy +Style/Encoding: + Enabled: false + Style/NumericLiterals: Enabled: false Description: 'This often hurts readability for exploit-ish code.' diff --git a/lib/rex/proto/sip.rb b/lib/rex/proto/sip.rb index 2550ee0948..3c0098cb54 100644 --- a/lib/rex/proto/sip.rb +++ b/lib/rex/proto/sip.rb @@ -1,4 +1,4 @@ -# -*- coding: binary -*- +# encoding: binary # SIP protocol support require 'rex/proto/sip/response' diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb index c770fc54b6..3135052471 100644 --- a/lib/rex/proto/sip/response.rb +++ b/lib/rex/proto/sip/response.rb @@ -1,4 +1,4 @@ -# -*- coding: binary -*- +# encoding: binary module Rex module Proto diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index b44a84f30a..d4886c1751 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -1,4 +1,3 @@ -# -*- coding: binary -*- ## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index 5f064f55b2..b211cbd416 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -1,4 +1,3 @@ -# -*- coding: binary -*- ## # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework diff --git a/tools/msftidy.rb b/tools/msftidy.rb index cee49583d0..ff6affc211 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -12,6 +12,7 @@ require 'time' CHECK_OLD_RUBIES = !!ENV['MSF_CHECK_OLD_RUBIES'] SUPPRESS_INFO_MESSAGES = !!ENV['MSF_SUPPRESS_INFO_MESSAGES'] +ENCODING_REGEX = /^# (?:\-\*\- )?encoding:\s*(\S+)/ if CHECK_OLD_RUBIES require 'rvm' @@ -109,6 +110,27 @@ class Msftidy end end + # Check that modules don't have any encoding comment and that + # non-modules have an explicity binary encoding comment + def check_encoding + # coding/encoding lines must be the first or second line if present + encoding_lines = @source.lines.to_a[0,2].select { |l| l =~ ENCODING_REGEX } + if @full_filepath =~ /(?:^|\/)modules\// + warn('Modules do not need an encoding comment') unless encoding_lines.empty? + else + if encoding_lines.empty? + warn('Non-modules must have an encoding comment') + else + encoding_line = encoding_lines.first + encoding_line =~ ENCODING_REGEX + encoding_type = Regexp.last_match(1) + unless encoding_type == 'binary' + warn("Non-modules must have a binary encoding comment, not #{encoding_type}") + end + end + end + end + def check_shebang if @source.lines.first =~ /^#!/ warn("Module should not have a #! line") @@ -583,6 +605,7 @@ def run_checks(full_filepath) tidy = Msftidy.new(full_filepath) tidy.check_mode tidy.check_shebang + tidy.check_encoding tidy.check_nokogiri tidy.check_rubygems tidy.check_ref_identifiers From 6a522cc105ef96a7b51a4c7b074c3022c0592975 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 25 Jul 2014 10:42:52 -0700 Subject: [PATCH 15/17] Remove unused BATCHSIZE from SIP options_tcp, duplicate from options --- modules/auxiliary/scanner/sip/options.rb | 1 - modules/auxiliary/scanner/sip/options_tcp.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index d4886c1751..8cb8ebf7c3 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -21,7 +21,6 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), OptString.new('TO', [false, 'The destination username to probe at each host', 'nobody']), Opt::RPORT(5060) ], self.class) diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index b211cbd416..f945bd23dd 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -21,7 +21,6 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]), OptString.new('TO', [false, 'The destination username to probe at each host', 'nobody']), Opt::RPORT(5060) ], self.class) From a41748e77e65c9f8597c2eae7661654f20f39b9e Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 25 Jul 2014 13:22:09 -0700 Subject: [PATCH 16/17] Correct SIP header note storage to align with Recog --- lib/msf/core/exploit/sip.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb index ffcc091fe8..57c65a9ef5 100644 --- a/lib/msf/core/exploit/sip.rb +++ b/lib/msf/core/exploit/sip.rb @@ -35,11 +35,12 @@ module Msf # report on any extracted headers extracted_headers.each do |k, v| + report_note( host: rhost, port: rport, proto: proto.downcase, - type: "sip_#{k}", + type: "sip_header.#{k.gsub(/-/, '_').downcase}", data: v ) end From e75e213b52bcdebc596ee3e83d964c487a30cec6 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Tue, 26 Aug 2014 11:40:49 -0700 Subject: [PATCH 17/17] Clarify SIP mixin method name, store header values as string, etc --- lib/msf/core/exploit/sip.rb | 7 +++---- modules/auxiliary/scanner/sip/options.rb | 2 +- modules/auxiliary/scanner/sip/options_tcp.rb | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb index 57c65a9ef5..093f47221c 100644 --- a/lib/msf/core/exploit/sip.rb +++ b/lib/msf/core/exploit/sip.rb @@ -7,8 +7,8 @@ module Msf module Exploit::Remote::SIP # Parses +response+, extracts useful metdata and then reports on it. # Returns true iff the response was a valid SIP response - def parse_response(response, rhost, proto, desired_headers = %w(User-Agent Server Allow)) - endpoint = "#{rhost}:#{rport}/#{proto}" + def report_response(response, rhost, proto, desired_headers = %w(User-Agent Server Allow)) + endpoint = "#{rhost}:#{rport} #{proto}" begin options_response = Rex::Proto::SIP::Response.parse(response) rescue ArgumentError => e @@ -35,13 +35,12 @@ module Msf # report on any extracted headers extracted_headers.each do |k, v| - report_note( host: rhost, port: rport, proto: proto.downcase, type: "sip_header.#{k.gsub(/-/, '_').downcase}", - data: v + data: v.join(',') ) end end diff --git a/modules/auxiliary/scanner/sip/options.rb b/modules/auxiliary/scanner/sip/options.rb index 8cb8ebf7c3..ebf466ff4c 100644 --- a/modules/auxiliary/scanner/sip/options.rb +++ b/modules/auxiliary/scanner/sip/options.rb @@ -36,6 +36,6 @@ class Metasploit3 < Msf::Auxiliary end def scanner_process(data, shost, _) - parse_response(data, shost, 'udp') + report_response(data, shost, 'udp') end end diff --git a/modules/auxiliary/scanner/sip/options_tcp.rb b/modules/auxiliary/scanner/sip/options_tcp.rb index f945bd23dd..60746418be 100644 --- a/modules/auxiliary/scanner/sip/options_tcp.rb +++ b/modules/auxiliary/scanner/sip/options_tcp.rb @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Auxiliary connect sock.put(create_probe(ip, 'tcp')) res = sock.get_once(-1, 5) - parse_response(res, rhost, 'tcp') if res + report_response(res, rhost, 'tcp') if res rescue ::Interrupt raise $ERROR_INFO ensure