commit
706655f755
|
@ -77,6 +77,14 @@ module Metasploit
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# @note Override this to detect that the service is up, is the right
|
||||
# version, etc.
|
||||
# @return [false] Indicates there were no errors
|
||||
# @return [String] a human-readable error message describing why
|
||||
# this scanner can't run
|
||||
def check_setup
|
||||
false
|
||||
end
|
||||
|
||||
def each_credential
|
||||
cred_details.each do |raw_cred|
|
||||
|
@ -85,6 +93,11 @@ module Metasploit
|
|||
credential = raw_cred.to_credential
|
||||
|
||||
if credential.realm.present? && self.class::REALM_KEY.present?
|
||||
# The class's realm_key will always be the right thing for the
|
||||
# service it knows how to login to. Override the credential's
|
||||
# realm_key if one exists for the class. This can happen for
|
||||
# example when we have creds for DB2 and want to try them
|
||||
# against Postgres.
|
||||
credential.realm_key = self.class::REALM_KEY
|
||||
yield credential
|
||||
elsif credential.realm.blank? && self.class::REALM_KEY.present? && self.class::DEFAULT_REALM.present?
|
||||
|
@ -93,12 +106,14 @@ module Metasploit
|
|||
yield credential
|
||||
elsif credential.realm.present? && self.class::REALM_KEY.blank?
|
||||
second_cred = credential.dup
|
||||
# Strip the realm off here, as we don't want it
|
||||
# This service has no realm key, so the realm will be
|
||||
# meaningless. Strip it off.
|
||||
credential.realm = nil
|
||||
credential.realm_key = nil
|
||||
yield credential
|
||||
# Some services can take a domain in the username like this even though
|
||||
# they do not explicitly take a domain as part of the protocol.
|
||||
# e.g., telnet
|
||||
second_cred.public = "#{second_cred.realm}\\#{second_cred.public}"
|
||||
second_cred.realm = nil
|
||||
second_cred.realm_key = nil
|
||||
|
|
|
@ -5,10 +5,6 @@ module Metasploit
|
|||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# I don't want to raise RuntimeError to be able to abort login
|
||||
class GlassfishError < StandardError
|
||||
end
|
||||
|
||||
# The Glassfish HTTP LoginScanner class provides methods to do login routines
|
||||
# for Glassfish 2, 3 and 4.
|
||||
class Glassfish < HTTP
|
||||
|
@ -16,14 +12,45 @@ module Metasploit
|
|||
DEFAULT_PORT = 4848
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
|
||||
# @!attribute version
|
||||
# @!attribute [r] version
|
||||
# @return [String] Glassfish version
|
||||
attr_accessor :version
|
||||
attr_reader :version
|
||||
|
||||
# @!attribute jsession
|
||||
# @return [String] Cookie session
|
||||
attr_accessor :jsession
|
||||
|
||||
# (see Base#check_setup)
|
||||
def check_setup
|
||||
res = send_request({'uri' => '/common/index.jsf'})
|
||||
return "Connection failed" if res.nil?
|
||||
if !([200, 302].include?(res.code))
|
||||
return "Unexpected HTTP response code #{res.code} (is this really Glassfish?)"
|
||||
end
|
||||
|
||||
# If remote login is enabled on 4.x, it redirects to https on the
|
||||
# same port.
|
||||
if !self.ssl && res.headers['Location'] =~ /^https:/
|
||||
self.ssl = true
|
||||
res = send_request({'uri' => '/common/index.jsf'})
|
||||
if res.nil?
|
||||
return "Connection failed after SSL redirection"
|
||||
end
|
||||
if res.code != 200
|
||||
return "Unexpected HTTP response code #{res.code} after SSL redirection (is this really Glassfish?)"
|
||||
end
|
||||
end
|
||||
|
||||
res = send_request({'uri' => '/login.jsf'})
|
||||
return "Connection failed" if res.nil?
|
||||
extract_version(res.headers['Server'])
|
||||
|
||||
if @version.nil? || @version !~ /^[2349]/
|
||||
return "Unsupported version ('#{@version}')"
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Sends a HTTP request with Rex
|
||||
#
|
||||
|
@ -126,9 +153,9 @@ module Metasploit
|
|||
return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body}
|
||||
end
|
||||
elsif res && is_secure_admin_disabled?(res)
|
||||
return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body}
|
||||
return {:status => Metasploit::Model::Login::Status::DENIED_ACCESS, :proof => res.body}
|
||||
elsif res && res.code == 400
|
||||
raise GlassfishError, "400: Bad HTTP request from try_login"
|
||||
return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => res.body}
|
||||
end
|
||||
|
||||
{:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body}
|
||||
|
@ -146,12 +173,10 @@ module Metasploit
|
|||
case self.version
|
||||
when /^[29]\.x$/
|
||||
status = try_glassfish_2(credential)
|
||||
result_opts.merge!(status: status[:status], proof:status[:proof])
|
||||
result_opts.merge!(status)
|
||||
when /^[34]\./
|
||||
status = try_glassfish_3(credential)
|
||||
result_opts.merge!(status: status[:status], proof:status[:proof])
|
||||
else
|
||||
raise GlassfishError, "Glassfish version '#{self.version}' not supported"
|
||||
result_opts.merge!(status)
|
||||
end
|
||||
rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
|
||||
|
@ -160,6 +185,31 @@ module Metasploit
|
|||
Result.new(result_opts)
|
||||
end
|
||||
|
||||
#
|
||||
# Extract the target's glassfish version from the HTTP Server header
|
||||
# (ex: Sun Java System Application Server 9.x)
|
||||
#
|
||||
# @param banner [String] `Server` header from a Glassfish service response
|
||||
# @return [String] version string, e.g. '2.x'
|
||||
# @return [nil] If the banner did not match any of the expected values
|
||||
def extract_version(banner)
|
||||
# Set version. Some GlassFish servers return banner "GlassFish v3".
|
||||
if banner =~ /(GlassFish Server|Open Source Edition)[[:blank:]]*(\d\.\d)/
|
||||
@version = $2
|
||||
elsif banner =~ /GlassFish v(\d)/
|
||||
@version = $1
|
||||
elsif banner =~ /Sun GlassFish Enterprise Server v2/
|
||||
@version = '2.x'
|
||||
elsif banner =~ /Sun Java System Application Server 9/
|
||||
@version = '9.x'
|
||||
else
|
||||
@version = nil
|
||||
end
|
||||
|
||||
return @version
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,26 @@ module Metasploit
|
|||
presence: true,
|
||||
length: { minimum: 1 }
|
||||
|
||||
# (see Base#check_setup)
|
||||
def check_setup
|
||||
http_client = Rex::Proto::Http::Client.new(
|
||||
host, port, {}, ssl, ssl_version
|
||||
)
|
||||
request = http_client.request_cgi(
|
||||
'uri' => uri,
|
||||
'method' => method
|
||||
)
|
||||
# Use _send_recv instead of send_recv to skip automatiu
|
||||
# authentication
|
||||
response = http_client._send_recv(request)
|
||||
|
||||
if !(response && response.code == 401 && response.headers['WWW-Authenticate'])
|
||||
"No authentication required"
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Attempt a single login with a single credential against the target.
|
||||
#
|
||||
# @param credential [Credential] The credential object to attempt to
|
||||
|
@ -73,21 +93,10 @@ module Metasploit
|
|||
'method' => method
|
||||
)
|
||||
|
||||
# First try to connect without logging in to make sure this
|
||||
# resource requires authentication. We use #_send_recv for
|
||||
# that instead of #send_recv.
|
||||
response = http_client._send_recv(request)
|
||||
if response && response.code == 401 && response.headers['WWW-Authenticate']
|
||||
# Now send the creds
|
||||
response = http_client.send_auth(
|
||||
response, request.opts, connection_timeout, true
|
||||
)
|
||||
response = http_client.send_recv(request)
|
||||
if response && response.code == 200
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
|
||||
end
|
||||
else
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::NO_AUTH_REQUIRED)
|
||||
end
|
||||
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
|
||||
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
|
||||
ensure
|
||||
|
|
|
@ -51,52 +51,11 @@ class Metasploit3 < Msf::Auxiliary
|
|||
# the LoginScanner class so the authentication can proceed properly
|
||||
#
|
||||
|
||||
def jsession
|
||||
@jsession || ''
|
||||
end
|
||||
|
||||
def set_jsession(res)
|
||||
if res && res.get_cookies =~ /JSESSIONID=(\w*);/i
|
||||
@scanner.jsession = $1
|
||||
end
|
||||
end
|
||||
|
||||
# Overrides the ssl method from HttpClient
|
||||
def ssl
|
||||
@scanner.ssl || datastore['SSL']
|
||||
end
|
||||
|
||||
#
|
||||
# Return GlassFish's edition (Open Source or Commercial) and version (2.x, 3.0, 3.1, 9.x, 4.0) and
|
||||
# banner (ex: Sun Java System Application Server 9.x)
|
||||
#
|
||||
def get_version(res)
|
||||
# Extract banner from response
|
||||
banner = res.headers['Server'] || ''
|
||||
|
||||
# Default value for edition and glassfish version
|
||||
edition = 'Commercial'
|
||||
version = 'Unknown'
|
||||
|
||||
# Set edition (Open Source or Commercial)
|
||||
p = /(Open Source|Sun GlassFish Enterprise Server|Sun Java System Application Server)/
|
||||
edition = 'Open Source' if banner =~ p
|
||||
|
||||
# Set version. Some GlassFish servers return banner "GlassFish v3".
|
||||
if banner =~ /(GlassFish Server|Open Source Edition)[[:blank:]]*(\d\.\d)/
|
||||
version = $2
|
||||
elsif banner =~ /GlassFish v(\d)/ && version.nil?
|
||||
version = $1
|
||||
elsif banner =~ /Sun GlassFish Enterprise Server v2/ && version == 'Unknown'
|
||||
version = '2.x'
|
||||
elsif banner =~ /Sun Java System Application Server 9/ && version == 'Unknown'
|
||||
version = '9.x'
|
||||
end
|
||||
|
||||
return edition, version, banner
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# For a while, older versions of Glassfish didn't need to set a password for admin,
|
||||
# but looks like no longer the case anymore, which means this method is getting useless
|
||||
|
@ -107,14 +66,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
if version =~ /^[29]\.x$/
|
||||
res = send_request_cgi({'uri'=>'/applications/upload.jsf'})
|
||||
set_jsession(res)
|
||||
p = /<title>Deploy Enterprise Applications\/Modules/
|
||||
if (res && res.code.to_i == 200 && res.body.match(p) != nil)
|
||||
success = true
|
||||
end
|
||||
elsif version =~ /^3\./
|
||||
res = send_request_cgi({'uri'=>'/common/applications/uploadFrame.jsf'})
|
||||
set_jsession(res)
|
||||
p = /<title>Deploy Applications or Modules/
|
||||
if (res && res.code.to_i == 200 && res.body.match(p) != nil)
|
||||
success = true
|
||||
|
@ -185,6 +142,10 @@ class Metasploit3 < Msf::Auxiliary
|
|||
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'"
|
||||
do_report(ip, rport, result)
|
||||
:next_user
|
||||
when Metasploit::Model::Login::Status::DENIED_ACCESS
|
||||
print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'"
|
||||
do_report(ip, rport, result)
|
||||
:next_user
|
||||
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
print_brute :level => :verror, :ip => ip, :msg => "Could not connect"
|
||||
invalidate_login(
|
||||
|
@ -215,74 +176,26 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
|
||||
def init_bruteforce
|
||||
res = nil
|
||||
tried = false
|
||||
|
||||
begin
|
||||
print_status("Sending a request to /common/index.jsf...")
|
||||
res = send_request_cgi({'uri'=>'/common/index.jsf'})
|
||||
set_jsession(res)
|
||||
|
||||
# Abort if res returns nil due to an exception (broken pipe or timeout)
|
||||
if res.nil?
|
||||
print_error('Unable to get a response from the server.')
|
||||
return
|
||||
end
|
||||
|
||||
# Automatic HTTP to HTTPS transition (when needed)
|
||||
if @scanner.ssl == false && res && res.headers['Location'] =~ /^https:\/\//
|
||||
print_status("Glassfish is asking us to use HTTPS")
|
||||
print_status("SSL option automatically set to: true")
|
||||
print_status("SSL version option automatically set to: #{datastore['SSLVersion']}")
|
||||
@scanner.ssl = true
|
||||
@scanner.ssl_version = datastore['SSLVersion']
|
||||
# Set the SSL options, and let the exception handler to resend the HTTP request
|
||||
# one more time.
|
||||
raise "SSL error"
|
||||
end
|
||||
rescue ::Exception => e
|
||||
# Retry the HTTP request with updated SSL options
|
||||
if e.message == 'SSL error' && tried == false
|
||||
tried = true
|
||||
retry
|
||||
else
|
||||
# Make sure we don't shut other problems up
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
# A normal client starts with /login.jsf, so we start with /login.jsf
|
||||
if res && res.code.to_i == 302
|
||||
res = send_request_cgi({'uri' => '/login.jsf'})
|
||||
set_jsession(res)
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# main
|
||||
#
|
||||
def run_host(ip)
|
||||
init_loginscanner(ip)
|
||||
res = init_bruteforce
|
||||
edition, version, banner = get_version(res)
|
||||
@scanner.version = version
|
||||
msg = @scanner.check_setup
|
||||
if msg
|
||||
print_brute :level => :error, :ip => rhost, :msg => msg
|
||||
return
|
||||
end
|
||||
|
||||
print_status('Checking if Glassfish requires a password...')
|
||||
if version =~ /^[239]\.x$/ && is_password_required?(version)
|
||||
print_brute :level=>:status, :ip=>rhost, :msg=>('Checking if Glassfish requires a password...')
|
||||
if @scanner.version =~ /^[239]\.x$/ && is_password_required?(@scanner.version)
|
||||
print_brute :level => :good, :ip => ip, :msg => "Note: This Glassfish does not require a password"
|
||||
else
|
||||
print_status("Glassfish is protected with a password")
|
||||
print_brute :level=>:status, :ip=>rhost, :msg=>("Glassfish is protected with a password")
|
||||
end
|
||||
|
||||
begin
|
||||
bruteforce(ip) unless version.blank?
|
||||
rescue ::Metasploit::Framework::LoginScanner::GlassfishError => e
|
||||
print_error(e.message)
|
||||
end
|
||||
bruteforce(ip) unless @scanner.version.blank?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -142,6 +142,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
connection_timeout: 5,
|
||||
)
|
||||
|
||||
msg = scanner.check_setup
|
||||
if msg
|
||||
print_brute :level => :error, :ip => ip, :msg => "Verification failed: #{msg}"
|
||||
return
|
||||
end
|
||||
|
||||
scanner.scan! do |result|
|
||||
credential_data = result.to_h
|
||||
credential_data.merge!(
|
||||
|
@ -162,9 +168,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
when Metasploit::Model::Login::Status::INCORRECT
|
||||
print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'"
|
||||
invalidate_login(credential_data)
|
||||
when Metasploit::Model::Login::Status::NO_AUTH_REQUIRED
|
||||
print_brute :level => :error, :ip => ip, :msg => "Failed: '#{result.credential}'"
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ describe Metasploit::Framework::LoginScanner::Glassfish do
|
|||
end
|
||||
|
||||
before do
|
||||
http_scanner.version = good_version
|
||||
http_scanner.instance_variable_set(:@version, good_version)
|
||||
end
|
||||
|
||||
context '#send_request' do
|
||||
|
@ -222,13 +222,6 @@ describe Metasploit::Framework::LoginScanner::Glassfish do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when unsupported Glassfish version' do
|
||||
it 'raises a GlassfishError exception' do
|
||||
http_scanner.version = bad_version
|
||||
expect { http_scanner.attempt_login(cred) }.to raise_exception(Metasploit::Framework::LoginScanner::GlassfishError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Glassfish version 2' do
|
||||
let(:login_ok_message) do
|
||||
'<title>Deploy Enterprise Applications/Modules</title>'
|
||||
|
@ -303,5 +296,14 @@ describe Metasploit::Framework::LoginScanner::Glassfish do
|
|||
end
|
||||
end
|
||||
|
||||
context '#extract_version' do
|
||||
let(:server_header) { "GlassFish Server Open Source Edition 4.0" }
|
||||
|
||||
specify do
|
||||
expect(http_scanner.extract_version(server_header)).to eq("4.0")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue