Split SIP response parsing out on its own, add unit tests.
Passes rspec but fails in framework. WIP.
This commit is contained in:
parent
69aa56d8d3
commit
02e41c27e7
|
@ -1,4 +1,5 @@
|
|||
# encoding: UTF-8
|
||||
|
||||
# SIP protocol support
|
||||
require 'rex/proto/sip/response'
|
||||
require 'rex/proto/sip/util'
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 = <<EOF
|
||||
H1: v1
|
||||
H2: v2
|
||||
H3: v3
|
||||
H2: v21
|
||||
EOF
|
||||
expected_headers = { 'H1' => %w(v1), 'H2' => %w(v2 v21), 'H3' => %w(v3) }
|
||||
specify { extract_headers(response_with_headers).should == expected_headers }
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue