lib/metasploit/framework/login_scanner/glassfish.rb
require 'metasploit/framework/login_scanner/http'
module Metasploit
module Framework
module LoginScanner
# The Glassfish HTTP LoginScanner class provides methods to do login routines
# for Glassfish 2, 3 and 4.
class Glassfish < HTTP
DEFAULT_PORT = 4848
PRIVATE_TYPES = [ :password ]
# @!attribute [r] version
# @return [String] Glassfish version
attr_accessor :version
# @!attribute jsession
# @return [String] Cookie session
attr_accessor :jsession
# @!attribute http_username
attr_accessor :http_username
# @return [String] HTTP username
# @!attribute http_password
attr_accessor :http_password
# (see Base#check_setup)
def check_setup
begin
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',
'cgi' => false
})
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',
'cgi' => false
})
return "Connection failed" if res.nil?
extract_version(res.headers['Server'])
if @version.nil? || @version !~ /^[2349]/
return "Unsupported version ('#{@version}')"
end
rescue ::EOFError, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError, Rex::ConnectionError, ::Timeout::Error
return "Unable to connect to target"
end
false
end
# Sends a HTTP request with Rex
#
# @param (see Rex::Proto::Http::Request#request_raw)
# @return [Rex::Proto::Http::Response] The HTTP response
def send_request(opts)
res = super(opts)
# Found a cookie? Set it. We're going to need it.
if res && res.get_cookies =~ /JSESSIONID=(\w*);/i
self.jsession = $1
end
res
end
# As of Sep 2014, if Secure Admin is disabled, it simply means the admin isn't allowed
# to login remotely. However, the authentication will still run and hint whether the
# password is correct or not.
#
# @param res [Rex::Proto::Http::Response] The HTTP auth response
# @return [boolean] True if disabled, otherwise false
def is_secure_admin_disabled?(res)
return (res.body =~ /Secure Admin must be enabled/i) ? true : false
end
# Sends a login request
#
# @param credential [Metasploit::Framework::Credential] The credential object
# @return [Rex::Proto::Http::Response] The HTTP auth response
def try_login(credential)
data = "j_username=#{Rex::Text.uri_encode(credential.public)}&"
data << "j_password=#{Rex::Text.uri_encode(credential.private)}&"
data << 'loginButton=Login'
opts = {
'uri' => '/j_security_check',
'method' => 'POST',
'data' => data,
'headers' => {
'Content-Type' => 'application/x-www-form-urlencoded',
'Cookie' => "JSESSIONID=#{self.jsession}",
},
'cgi' => false
}
send_request(opts)
end
# Tries to login to Glassfish version 2
#
# @param credential [Metasploit::Framework::Credential] The credential object
# @return [Hash]
# * :status [Metasploit::Model::Login::Status]
# * :proof [String] the HTTP response body
def try_glassfish_2(credential)
res = try_login(credential)
if res && res.code == 302
opts = {
'uri' => '/applications/upload.jsf',
'method' => 'GET',
'headers' => {
'Cookie' => "JSESSIONID=#{self.jsession}"
},
'cgi' => false
}
res = send_request(opts)
p = /<title>Deploy Enterprise Applications\/Modules/
if (res && res.code.to_i == 200 && res.body.match(p) != nil)
return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body}
end
end
{:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body}
end
# Tries to login to Glassfish version 9
#
# @param credential [Metasploit::Framework::Credential] The credential object
# @return [Hash]
# * :status [Metasploit::Model::Login::Status]
# * :proof [String] the HTTP response body
def try_glassfish_9(credential)
res = try_login(credential)
if res && res.code.to_i == 302 && res.headers['Location'].to_s !~ /loginError\.jsf$/
return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body}
end
{:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body}
end
# Tries to login to Glassfish version 3 or 4 (as of now it's the latest)
#
# @param (see #try_glassfish_2)
# @return (see #try_glassfish_2)
def try_glassfish_3(credential)
res = try_login(credential)
if res && res.code == 302
opts = {
'uri' => '/common/applications/uploadFrame.jsf',
'method' => 'GET',
'headers' => {
'Cookie' => "JSESSIONID=#{self.jsession}"
},
'cgi' => false
}
res = send_request(opts)
p = /<title>Deploy Applications or Modules/
if (res && res.code.to_i == 200 && res.body.match(p) != nil)
return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body}
end
elsif res && is_secure_admin_disabled?(res)
return {:status => Metasploit::Model::Login::Status::DENIED_ACCESS, :proof => res.body}
elsif res && res.code == 400
return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => res.body}
end
{:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body}
end
# Decides which login routine and returns the results
#
# @param credential [Metasploit::Framework::Credential] The credential object
# @return [Result]
def attempt_login(credential)
result_opts = { credential: credential }
begin
case self.version
when /^2\.x$/
status = try_glassfish_2(credential)
result_opts.merge!(status)
when /^[34]\./
status = try_glassfish_3(credential)
result_opts.merge!(status)
when /^9\.x$/
status = try_glassfish_9(credential)
result_opts.merge!(status)
end
rescue ::EOFError, Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
end
Result.new(result_opts)
end
# Extract the target's glassfish version from the HTTP Server Sun Java System Application Server 9.1header
# (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
end