lib/msf/core/exploit/remote/http/manage_engine_adaudit_plus/login.rb
# -*- coding: binary -*-
module Msf::Exploit::Remote::HTTP::ManageEngineAdauditPlus::Login
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HTTP::ManageEngineAdauditPlus::StatusCodes
include Msf::Exploit::Remote::HTTP::ManageEngineAdauditPlus::TargetInfo
include Msf::Exploit::Remote::HTTP::ManageEngineAdauditPlus::URIs
# Performs a ManageEngine ADAudit Plus login.
#
# @param auth_domain [String] The authentication domain to use to log in.
# @param user [String] The username to log in as.
# @param pass [String] The password to log in with.
# @param only_get_cookie [Boolean] If this is set to true, then this method will only try to obtain an
# 'adapcsrf' cookie that is required to perform API calls.
# @return [Hash] Hash containing a `status` key, which is used to hold a
# status value as an Integer value, a `message` key, which is used
# to hold a message associated with the status value as a String. May optionally
# contain an `adapcsrf_cookie` key which maps to a String containing the
# adapcsrf cookie to be used for authentication purposes, and/or a
# `configured_domains` key which maps to an Array of Strings,
# each containing a domain name that has been configured to be used by
# the ManageEngine ADAudit Plus target.
def adaudit_plus_login(auth_domain, user = '', pass = '', only_get_cookie = false)
cookie_jar.clear # let's start fresh
# Visit the default homepage to retrieve some of the baseline cookies needed to authenticate.
res_initial_cookies = send_request_cgi({
'uri' => normalize_uri(target_uri.path),
'method' => 'GET',
'keep_cookies' => true
})
unless res_initial_cookies
return {
'status' => adaudit_plus_status::CONNECTION_FAILED,
'message' => 'Connection failed.'
}
end
# Make sure the target is actually ManageEngine ADAudit Plus
unless res_initial_cookies.code == 200 && res_initial_cookies.body =~ /<title>ADAudit Plus/
return {
'status' => adaudit_plus_status::UNEXPECTED_REPLY,
'message' => 'Target does not seem to be ADAudit Plus.'
}
end
# Check if we have an initial adapcsrf cookie with the expected format
unless res_initial_cookies.headers.include?('Set-Cookie') && res_initial_cookies.get_cookies =~ /adapcsrf=[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/
return {
'status' => adaudit_plus_status::UNEXPECTED_REPLY,
'message' => 'Failed to obtain the baseline cookies needed to proceed with authentication.'
}
end
# Visit the adaudit_plus_jump_to_js_uri page to grab more cookies needed for authentication.
vprint_status('Attempting to obtain the required cookies for authentication')
res_extra_cookies = send_request_cgi({
'uri' => adaudit_plus_jump_to_js_uri,
'method' => 'GET',
'keep_cookies' => true
})
unless res_extra_cookies
return {
'status' => adaudit_plus_status::CONNECTION_FAILED,
'message' => 'Connection failed.'
}
end
# check if we have a new adapcsrf cookie with the expected format, which is different
# from the initial adapcsrf cookie format that we got before visiting the adaudit_plus_jump_to_js_uri URI.
unless res_extra_cookies.code == 200 && res_extra_cookies.headers.include?('Set-Cookie') && res_extra_cookies.get_cookies =~ /adapcsrf=[a-f0-9]{128}/
return {
'status' => adaudit_plus_status::UNEXPECTED_REPLY,
'message' => 'Failed to obtain the jump_to_js cookies required for authentication.'
}
end
vprint_status('Trying to authenticate...')
post_vars = {
'forChecking' => '',
'j_username' => user.to_s,
'j_password' => pass.to_s,
'domainName' => auth_domain.to_s,
'AUTHRULE_NAME' => 'Authenticator'
}
res_login = send_request_cgi({
'uri' => adaudit_plus_login_uri,
'method' => 'POST',
'keep_cookies' => true,
'vars_post' => post_vars
})
# Check to see if the connection succeeded.
return {
'status' => adaudit_plus_status::CONNECTION_FAILED,
'message' => 'Connection failed'
} unless res_login
# Check to see if we got the right response code and the expected cookies.
unless res_login.code == 303 && res_login.headers.include?('Set-Cookie')
# Matches something like JSESSIONIDADAP=50E42FBF96E820A6099A1F38FA5A4854; JSESSIONIDADAPSSO=7EB091F6BB9A7A4C4476419DFC11E2A1;
# Or this JSESSIONIDADAP=50E42FBF96E820A6099A1F38FA5A4854; JSESSIONIDSSO=7EB091F6BB9A7A4C4476419DFC11E2A1;
# Or even this JSESSIONIDADAP=50E42FBF96E820A6099A1F38FA5A4854; JSESSIONIDSSO=7EB091F6BB9A7A4C4476419DFC11E2A1
unless res_login.get_cookies =~ /(?:JSESSIONID[A-Z].*?=[0-9A-Z]{32};{0,1} {0,1}){2}/
return {
'status' => adaudit_plus_status::NO_ACCESS,
'message' => 'Failed to authenticate.'
}
end
end
# Check if we are actually logged in by visiting the home page.
res_post_auth = send_request_cgi({
'uri' => normalize_uri(target_uri.path),
'method' => 'GET',
'keep_cookies' => true
})
return {
'status' => adaudit_plus_status::CONNECTION_FAILED,
'message' => 'Connection failed'
} unless res_post_auth
unless res_post_auth.code == 200 && res_post_auth.body.include?('ManageEngine ADAudit Plus web client is initializing')
return {
'status' => adaudit_plus_status::NO_ACCESS,
'message' => 'The web app failed to load after authenticating'
}
end
# Return the value of the adapcsrf cookie, which will be required for later actions.
adapcsrf_cookie = cookie_jar.cookies.select { |k| k.name == 'adapcsrf' }&.first
if adapcsrf_cookie.blank? || adapcsrf_cookie.value.blank?
return {
'status' => adaudit_plus_status::NO_ACCESS,
'message' => 'Failed to obtain the required adapcsrf cookie'
}
end
# In order to get a cookie we can actually use, we need to obtain the configured domains via the API,
# so we will call adaudit_plus_grab_configured_domains to retrieve this information for us.
# Note that adaudit_plus_obtain_configured_domains uses the same return format as this method.
adaudit_plus_grab_configured_domains(adapcsrf_cookie.value, only_get_cookie)
end
end