Land #9915, Cleanup and improvements to influxdb_enum
parent
a9eb87efbd
commit
9acb0cd689
|
@ -0,0 +1,46 @@
|
|||
This module enumerates databases on InfluxDB using the REST API using the default authentication of root:root.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/influxdb_enum```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Scenarios
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/scanner/http/influxdb_enum
|
||||
msf5 auxiliary(scanner/http/influxdb_enum) > set RHOST 172.25.65.20
|
||||
RHOST => 172.25.65.20
|
||||
msf5 auxiliary(scanner/http/influxdb_enum) > set VERBOSE true
|
||||
VERBOSE => true
|
||||
msf5 auxiliary(scanner/http/influxdb_enum) > run
|
||||
|
||||
[+] 172.25.65.20:8086 - Influx Version: 1.5.1
|
||||
[+] 172.25.65.20:8086 - Influx DB Found:
|
||||
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"statement_id": 0,
|
||||
"series": [
|
||||
{
|
||||
"name": "databases",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"values": [
|
||||
[
|
||||
"_internal"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
[+] File saved in: /Users/unix/.msf4/loot/20180423050119_default_172.25.65.20_influxdb.enum_623871.txt
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -16,64 +16,85 @@ class MetasploitModule < Msf::Auxiliary
|
|||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://influxdb.com/docs/v0.9/concepts/reading_and_writing_data.html']
|
||||
['URL', 'https://docs.influxdata.com/influxdb/'],
|
||||
['URL', 'https://www.shodan.io/search?query=X-Influxdb-Version']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'Roberto Soares Espreto <robertoespreto[at]gmail.com>',
|
||||
'Nixawk'
|
||||
],
|
||||
'Author' => [ 'Roberto Soares Espreto <robertoespreto[at]gmail.com>' ],
|
||||
'License' => MSF_LICENSE
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8086),
|
||||
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/db']),
|
||||
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/']),
|
||||
OptString.new('USERNAME', [true, 'The username to login as', 'root']),
|
||||
OptString.new('PASSWORD', [true, 'The password to login with', 'root'])
|
||||
OptString.new('PASSWORD', [true, 'The password to login with', 'root']),
|
||||
OptString.new('QUERY', [true, 'The influxdb query syntax', 'SHOW DATABASES'])
|
||||
])
|
||||
end
|
||||
|
||||
def run
|
||||
begin
|
||||
# Check the target if is a influxdb server
|
||||
res = send_request_cgi(
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'method' => 'GET'
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'method' => 'GET'
|
||||
)
|
||||
rescue ::Errno::EPIPE, ::Timeout::Error, ::EOFError, ::IOError => e
|
||||
print_error("The following Error was encountered: #{e.class}")
|
||||
return
|
||||
end
|
||||
|
||||
unless res
|
||||
print_error("Server did not respond in an expected way.")
|
||||
return
|
||||
end
|
||||
return if res.nil?
|
||||
return if res.headers['X-Influxdb-Version'].nil?
|
||||
|
||||
if res.code == 401 && res.body =~ /Invalid username\/password/
|
||||
print_error("Failed to authenticate. Invalid username/password.")
|
||||
return
|
||||
elsif res.code == 200 && res.headers.include?('X-Influxdb-Version') && res.body.length > 0
|
||||
print_status("Enumerating...")
|
||||
begin
|
||||
temp = JSON.parse(res.body)
|
||||
if temp.blank?
|
||||
print_status("Json data is empty")
|
||||
return
|
||||
print_good("#{peer} - Influx Version: #{res.headers['X-Influxdb-Version']}")
|
||||
|
||||
# Send http auth to the target
|
||||
# curl http://127.0.0.1:8086/query?q=SHOW+DATABASES
|
||||
# curl -X POST http://127.0.0.1:8086/query --data 'q=SHOW DATABASES'
|
||||
res = send_request_cgi(
|
||||
'uri' => normalize_uri(target_uri.path, '/query'),
|
||||
'method' => 'GET',
|
||||
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
|
||||
'vars_get' => {
|
||||
'q' => datastore['QUERY']
|
||||
}
|
||||
)
|
||||
|
||||
return if res.nil?
|
||||
return if res.headers['X-Influxdb-Version'].nil?
|
||||
|
||||
# Check http auth status
|
||||
case res.code
|
||||
when 401
|
||||
fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate. Invalid username/password.")
|
||||
when 200
|
||||
|
||||
begin
|
||||
jsonres = JSON.parse(res.body)
|
||||
return if jsonres.nil?
|
||||
return if jsonres['results'].nil?
|
||||
|
||||
result = JSON.pretty_generate(jsonres)
|
||||
vprint_good("#{peer} - Influx DB Found:\n\n#{result}\n")
|
||||
path = store_loot(
|
||||
'influxdb.enum',
|
||||
'text/plain',
|
||||
rhost,
|
||||
result,
|
||||
'InfluxDB Enum'
|
||||
)
|
||||
print_good("File saved in: #{path}")
|
||||
rescue JSON::ParserError
|
||||
fail_with(Failure::Unknown, "#{peer} - Unexpected response, cannot parse JSON")
|
||||
end
|
||||
results = JSON.pretty_generate(temp)
|
||||
rescue JSON::ParserError
|
||||
print_error("Unable to parse JSON data.")
|
||||
return
|
||||
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Unexpected response status #{res.code}")
|
||||
end
|
||||
print_good("Found:\n\n#{results}\n")
|
||||
path = store_loot(
|
||||
'influxdb.enum',
|
||||
'text/plain',
|
||||
rhost,
|
||||
results,
|
||||
'InfluxDB Enum'
|
||||
)
|
||||
print_good("File saved in: #{path}")
|
||||
else
|
||||
print_error("Unable to enum, received \"#{res.code}\"")
|
||||
rescue ::Rex::ConnectionError
|
||||
fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the influx db server.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue