metasploit-framework/lib/msf/core/auxiliary/redis.rb

104 lines
3.0 KiB
Ruby

# -*- coding: binary -*-
require 'msf/core/exploit'
module Msf
###
#
# This module provides methods for working with redis
#
###
module Auxiliary::Redis
include Msf::Exploit::Remote::Tcp
include Auxiliary::Scanner
include Auxiliary::Report
#
# Initializes an instance of an auxiliary module that interacts with Redis
#
def initialize(info = {})
super
register_options(
[
Opt::RPORT(6379),
OptString.new('Password', [false, 'Redis password for authentication test', 'foobared'])
]
)
register_advanced_options(
[
OptInt.new('READ_TIMEOUT', [true, 'Seconds to wait while reading redis responses', 2])
]
)
end
def peer
"#{rhost}:#{rport}"
end
def read_timeout
datastore['READ_TIMEOUT']
end
def report_redis(version)
report_service(
host: rhost,
port: rport,
proto: 'tcp',
name: 'redis',
info: "version #{version}"
)
end
def redis_command(*commands)
command_string = printable_redis_response(commands.join(' '))
unless (command_response = send_redis_command(*commands))
vprint_error("#{peer} -- no response to '#{command_string}'")
return
end
if /(?<auth_response>ERR operation not permitted|NOAUTH Authentication required)/i =~ command_response
fail_with(::Msf::Module::Failure::BadConfig, "#{peer} requires authentication but Password unset") unless datastore['Password']
vprint_status("#{peer} -- requires authentication (#{printable_redis_response(auth_response, false)})")
if (auth_response = send_redis_command('AUTH', datastore['Password']))
unless auth_response =~ /\+OK/
vprint_error("#{peer} -- authentication failure: #{printable_redis_response(auth_response)}")
return
end
vprint_status("#{peer} -- authenticated")
unless (command_response = send_redis_command(*commands))
vprint_error("#{peer} -- no response to '#{command_string}'")
return
end
else
vprint_status("#{peer} -- authentication failed; no response")
return
end
end
vprint_status("#{peer} -- redis command '#{command_string}' got '#{printable_redis_response(command_response)}'")
command_response
end
def printable_redis_response(response_data, convert_whitespace = true)
Rex::Text.ascii_safe_hex(response_data, convert_whitespace)
end
private
def redis_proto(command_parts)
return if command_parts.blank?
command = "*#{command_parts.length}\r\n"
command_parts.each do |c|
command << "$#{c.length}\r\n#{c}\r\n"
end
command
end
def send_redis_command(*command_parts)
sock.put(redis_proto(command_parts))
command_response = sock.get_once(-1, read_timeout)
return unless command_response
command_response.strip
end
end
end