CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/lib/metasploit/framework/login_scanner/redis.rb
Views: 1904
require 'metasploit/framework/login_scanner/base'1require 'metasploit/framework/login_scanner/rex_socket'2require 'metasploit/framework/tcp/client'3require 'rex/proto/redis'45module Metasploit6module Framework7module LoginScanner89# This is the LoginScanner class for dealing with REDIS.10# It is responsible for taking a single target, and a list of credentials11# and attempting them. It then saves the results.12class Redis13include Metasploit::Framework::LoginScanner::Base14include Metasploit::Framework::LoginScanner::RexSocket15include Metasploit::Framework::Tcp::Client1617# Required to be able to invoke the scan! method from the included Base module.18# We do not use inheritance, so overwriting a method and relying on super does19# not work in this case.20alias parent_scan! scan!2122DEFAULT_PORT = 637923LIKELY_PORTS = [ DEFAULT_PORT ]24LIKELY_SERVICE_NAMES = [ 'redis' ]25PRIVATE_TYPES = [ :password ]26REALM_KEY = nil2728# Attempt to login with every {Credential credential} in29# {#cred_details}, by calling {#attempt_login} once for each.30#31# If a successful login is found for a user, no more attempts32# will be made for that user. If the scanner detects that no33# authentication is required, no further attempts will be made34# at all.35#36# @yieldparam result [Result] The {Result} object for each attempt37# @yieldreturn [void]38# @return [void]39def scan!(&block)40first_credential = to_enum(:each_credential).first41result = attempt_login(first_credential)42result.freeze4344if result.status == Metasploit::Model::Login::Status::NO_AUTH_REQUIRED45yield result if block_given?46else47parent_scan!(&block)48end49end5051# This method can create redis command which can be read by redis server52def redis_proto(command_parts)53return if command_parts.blank?5455command = "*#{command_parts.length}\r\n"56command_parts.each do |p|57command << "$#{p.length}\r\n#{p}\r\n"58end59command60end6162# This method attempts a single login with a single credential against the target63# @param credential [Credential] The credential object to attempt to login with64# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object65def attempt_login(credential)66result_options = {67credential: credential,68status: Metasploit::Model::Login::Status::INCORRECT,69host: host,70port: port,71protocol: 'tcp',72service_name: 'redis'73}7475disconnect if sock7677begin78connect79select([sock], nil, nil, 0.4)8081# Skip this call if we're dealing with an older redis version.82response = authenticate(credential.public.to_s, credential.private.to_s) unless @older_redis8384# If we're dealing with an older redis version or the previous call failed,85# try the backwards compatibility call instead.86# We also set the @older_redis to true if we haven't as we might be entering this87# block from the match response.88if @older_redis || (response && response.match(::Rex::Proto::Redis::Base::Constants::WRONG_ARGUMENTS_FOR_AUTH))89@older_redis ||= true90response = authenticate_pre_v6(credential.private.to_s)91end9293result_options[:proof] = response94result_options[:status] = validate_login(result_options[:proof])95rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e96result_options.merge!(97proof: e,98status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT99)100end101102disconnect if sock103104::Metasploit::Framework::LoginScanner::Result.new(result_options)105end106107private108109# Authenticates against Redis using the provided credentials arguments.110# Takes either a password, or a username and password combination.111#112# @param [String] username The username to authenticate with, defaults to 'default'113# @param [String] password The password to authenticate with.114# @return [String] The response from Redis for the AUTH command.115def authenticate(username, password)116command = redis_proto(['AUTH', username.blank? ? 'default' : username, password])117sock.put(command)118sock.get_once119end120121# Authenticates against Redis using the provided password.122# This method is for older Redis instances of backwards compatibility.123#124# @param [String] password The password to authenticate with.125# @return [String] The response from Redis for the AUTH command.126def authenticate_pre_v6(password)127command = redis_proto(['AUTH', password])128sock.put(command)129sock.get_once130end131132# Validates the login data received from Redis and returns the correct Login status133# based upon the contents Redis sent back:134#135# No password - ( -ERR Client sent AUTH, but no password is set\r\n )136# Invalid password - ( -ERR invalid password\r\n )137# Valid password - (+OK\r\n)138def validate_login(data)139return if data.nil?140141return Metasploit::Model::Login::Status::NO_AUTH_REQUIRED if no_password_set?(data)142return Metasploit::Model::Login::Status::INCORRECT if invalid_password?(data)143return Metasploit::Model::Login::Status::SUCCESSFUL if data.match(::Rex::Proto::Redis::Base::Constants::OKAY)144145nil146end147148def no_password_set?(data)149data.match(::Rex::Proto::Redis::Base::Constants::NO_PASSWORD_SET) ||150data.match(::Rex::Proto::Redis::Version6::Constants::NO_PASSWORD_SET)151end152153def invalid_password?(data)154data.match(::Rex::Proto::Redis::Base::Constants::WRONG_PASSWORD) ||155data.match(::Rex::Proto::Redis::Version6::Constants::WRONG_PASSWORD)156end157158# (see Base#set_sane_defaults)159def set_sane_defaults160self.connection_timeout ||= 30161self.port ||= DEFAULT_PORT162self.max_send_size ||= 0163self.send_delay ||= 0164end165end166end167end168end169170171