module CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack

Module to provide an easy way to perform password attacks

Public Instance Methods

attack(users, wordlist_path, opts = {}) { |user| ... } click to toggle source

@param [ Array<CMSScanner::Model::User> ] users @param [ String ] wordlist_path @param [ Hash ] opts @option opts [ Boolean ] :show_progression

@yield [ CMSScanner::User ] When a valid combination is found

Due to Typhoeus threads shenanigans, in rare cases the progress-bar might be incorrectly updated, hence the 'rescue ProgressBar::InvalidProgressError'

TODO: Make rubocop happy about metrics etc

rubocop:disable all

# File lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb, line 21
def attack(users, wordlist_path, opts = {})
  wordlist = File.open(wordlist_path)

  create_progress_bar(total: users.size * wordlist.count, show_progression: opts[:show_progression])

  queue_count         = 0
  # Keep the number of requests sent for each users
  # to be able to correctly update the progress when a password is found
  user_requests_count = {}

  users.each { |u| user_requests_count[u.username] = 0 }

  File.foreach(wordlist, chomp: true) do |password|
    remaining_users = users.select { |u| u.password.nil? }

    break if remaining_users.empty?

    remaining_users.each do |user|
      request = login_request(user.username, password)

      user_requests_count[user.username] += 1

      request.on_complete do |res|
        progress_bar.title = "Trying #{user.username} / #{password}"

        progress_bar.increment unless progress_bar.progress == progress_bar.total

        if valid_credentials?(res)
          user.password = password

          begin
            progress_bar.total -= wordlist.count - user_requests_count[user.username]
          rescue ProgressBar::InvalidProgressError
          end

          yield user
        elsif errored_response?(res)
          output_error(res)
        end
      end

      hydra.queue(request)
      queue_count += 1

      if queue_count >= hydra.max_concurrency
        hydra.run
        queue_count = 0
      end
    end
  end

  hydra.run
  progress_bar.stop
end
errored_response?(response) click to toggle source

@param [ Typhoeus::Response ] response

@return [ Boolean ] Whether or not something wrong happened

other than wrong credentials
# File lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb, line 96
def errored_response?(response)
  # To Implement in the finder related to the attack
end
login_request(username, password) click to toggle source

@param [ String ] username param [ String ] password

@return [ Typhoeus::Request ]

# File lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb, line 81
def login_request(username, password)
  # To Implement in the finder related to the attack
end
valid_credentials?(response) click to toggle source

@param [ Typhoeus::Response ] response

@return [ Boolean ] Whether or not credentials related to the request are valid

# File lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb, line 88
def valid_credentials?(response)
  # To Implement in the finder related to the attack
end

Protected Instance Methods

output_error(response) click to toggle source

@param [ Typhoeus::Response ] response

# File lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb, line 103
def output_error(response)
  error = if response.timed_out?
            'Request timed out.'
          elsif response.code.zero?
            "No response from remote server. WAF/IPS? (#{response.return_message})"
          elsif response.code.to_s.start_with?('50')
            'Server error, try reducing the number of threads.'
          elsif NS::ParsedCli.verbose?
            "Unknown response received Code: #{response.code}\nBody: #{response.body}"
          else
            "Unknown response received Code: #{response.code}"
          end

  progress_bar.log("Error: #{error}")
end