Land #3779, Glassfish LoginScanner exception

MSP-11343
bug/bundler_fix
Luke Imhoff 2014-09-11 15:57:47 -05:00
commit 706655f755
No known key found for this signature in database
GPG Key ID: 5B1FB01FB33356F8
6 changed files with 130 additions and 138 deletions

View File

@ -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

View File

@ -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

View File

@ -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,20 +93,9 @@ 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
)
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)
response = http_client.send_recv(request)
if response && response.code == 200
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
end
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)

View File

@ -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

View File

@ -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

View File

@ -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