Share
## https://sploitus.com/exploit?id=PACKETSTORM:181116
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Report  
include Msf::Auxiliary::Scanner  
  
def initialize  
super(  
'Name' => 'FortiMail Unauthenticated Login Bypass Scanner',  
'Description' => %q{  
This module attempts to detect instances of FortiMail vulnerable  
against an unauthenticated login bypass (CVE-2020-9294).  
},  
'Author' => [  
'Mike Connor', # Initial Vulnerability discovery  
'Juerg Schweingruber <juerg.schweingruber[at]redguard.ch>', # Vulnerability Re-Discovery  
'Patrick Schmid <patrick.schmid[at]redguard.ch>' # Exploit Development & MSF module  
],  
'References' => [  
['CVE', '2020-9294'],  
['URL', 'https://www.fortiguard.com/psirt/FG-IR-20-045'],  
['URL', 'https://www.redguard.ch/blog/2020/07/02/fortimail-unauthenticated-login-bypass/']  
],  
'License' => MSF_LICENSE  
)  
  
register_options([  
OptString.new('TARGETURI', [true, 'Path to the FortiMail admin page', '/admin/AdminLogin.html']),  
Opt::RPORT(443)  
])  
  
register_advanced_options([  
OptBool.new('SSL', [true, 'Negotiate SSL connection', true])  
])  
end  
  
def target_url  
proto = 'http'  
if (rport == 443) || ssl  
proto = 'https'  
end  
uri = normalize_uri(datastore['URI'])  
"#{proto}://#{vhost}:#{rport}#{uri}"  
end  
  
def run_host(ip)  
vprint_status("Checking vulnerability at #{ip}")  
uri = normalize_uri(target_uri.path)  
  
res = send_request_raw({  
'method' => 'GET',  
'uri' => uri  
})  
  
return :abort unless res # prints default connection error messages  
  
if res.code == 301  
vprint_error("#{ip} - Page redirect to #{res.headers['Location']}")  
return :abort  
end  
  
unless res.code == 200  
vprint_bad("#{ip} - No version of FortiMail detected")  
return :abort  
end  
  
version_raw = res.body[/fml-admin-login-(\d+).js/, 1]  
version = version_raw.to_i  
unless res.body.include?('newpassword') && (version.between?(140, 160) || version.between?(730, 745) || version.between?(250, 263))  
print_bad("#{ip} - Not vulnerable version (Build: #{version_raw}) of FortiMail detected")  
return :abort  
end  
  
print_good("#{ip} - Vulnerable version (Build: #{version_raw}) of FortiMail detected")  
  
report_vuln(  
host: rhost,  
port: rport,  
name: 'FortiMail Login Bypass',  
refs: references  
)  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout  
# noop  
rescue ::Timeout::Error, ::Errno::EPIPE  
# noop  
rescue ::OpenSSL::SSL::SSLError => e  
return if (e.to_s.match(/^SSL_connect /)) # strange errors / exception if SSL connection aborted  
end  
end